peteryuanpan / notebook

喜欢的,值得留念的,就记下来,总会有用的。
72 stars 43 forks source link

Redis学习总结 #155

Closed peteryuanpan closed 3 years ago

peteryuanpan commented 3 years ago

参考(优秀)

参考(动手)

参考(一般)

鲁班学院课程

总结

peteryuanpan commented 3 years ago

参考

小结

peteryuanpan commented 3 years ago

本例子是基于 https://github.com/peteryuanpan/notebook/issues/171#issuecomment-738669987 之上的,因此只贴出有修改或新增的代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-mybatis-redis</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

    </dependencies>

</project>

application.yml

server:
  port: 8082

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password: # 为空
    pool:
      max-active: 8 # 连接池最大连接数
      max-wait: -1 # 连接池最大阻塞等待时间
      max-idle: 8 # 连接池最大空闲连接
      min-idle: 0 # 连接池最小空闲连接
    timeout: 300ms # 连接超时时间(毫秒)

mybatis:
  mapper-locations:
    - classpath:mapper/*.xml

logging:
  level:
    com:
      peter:
        mapper: debug

DaoController

package com.peter.controller;

import com.peter.mapper.EmployeeMapper;
import com.peter.model.Employee;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/dao")
public class DaoController {

    private static final Logger logger = LoggerFactory.getLogger(DaoController.class);

    @Resource
    private RedisTemplate<String, Employee> redisTemplate;

    @Autowired
    private EmployeeMapper employeeMapper;

    @GetMapping("/test1")
    public void test1(HttpServletRequest request, HttpServletResponse response) {
        List<Employee> employee = employeeMapper.getEmpById(1);
        logger.info(employee.toString());
    }

    @GetMapping("/test2")
    public void test2(HttpServletRequest request, HttpServletResponse response) {
        logger.info(redisTemplate.hasKey("1").toString());
        Employee employee = redisTemplate.opsForValue().get("1");
        if (employee == null) {
            employee = employeeMapper.getEmpById(1).get(0);
            redisTemplate.opsForValue().set("1", employee);
        }
        logger.info(employee.toString());
    }
}

RedisConfig

package com.peter.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        setSerializer(template);// 设置序列化工具
        template.afterPropertiesSet();
        return template;
    }

    private void setSerializer(StringRedisTemplate template) {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
    }
}

Employee(加了implements Serializable)

package com.peter.model;

import java.io.Serializable;

public class Employee implements Serializable {

    private Integer id;
    private String lastName;
    private String email;
    private String gender;

    @Override
    public String toString() {
        return "Emplyee{" +
            "id=" + id +
            ", lastName='" + lastName + '\'' +
            ", email='" + email + '\'' +
            ", gender='" + gender + '\'' +
            '}';
    }
}

请求两次 curl http://localhost:8082/dao/test2 ,第一次从数据库中获取,第二次直接从redis缓存中获取

输出结果

2020-12-07 21:16:57.110  INFO 13340 --- [nio-8082-exec-1] com.peter.controller.DaoController       : false
2020-12-07 21:16:57.594 DEBUG 13340 --- [nio-8082-exec-1] c.p.mapper.EmployeeMapper.getEmpById     : ==>  Preparing: select * from tbl_employee where id = ? 
2020-12-07 21:16:57.615 DEBUG 13340 --- [nio-8082-exec-1] c.p.mapper.EmployeeMapper.getEmpById     : ==> Parameters: 1(Integer)
2020-12-07 21:16:57.636 DEBUG 13340 --- [nio-8082-exec-1] c.p.mapper.EmployeeMapper.getEmpById     : <==      Total: 1
2020-12-07 21:16:57.642  INFO 13340 --- [nio-8082-exec-1] com.peter.controller.DaoController       : Emplyee{id=1, lastName='null', email='peter@qq.com', gender='0'}
2020-12-07 21:17:09.301  INFO 13340 --- [nio-8082-exec-2] com.peter.controller.DaoController       : true
2020-12-07 21:17:09.303  INFO 13340 --- [nio-8082-exec-2] com.peter.controller.DaoController       : Emplyee{id=1, lastName='null', email='peter@qq.com', gender='0'}

上面代码涉及到了几个知识点

一,注解Resource和Autowired

参考 https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-redis

If you add your own @Bean of any of the auto-configured types, it replaces the default (except in the case of RedisTemplate, when the exclusion is based on the bean name, redisTemplate, not its type). By default, if commons-pool2 is on the classpath, you get a pooled connection factory.

在项目中声明一个RedisTemplate < String, Object > 的Bean,应该用 Resource,默认按照名称获取bean 而要用 Autowired 的话,则声明为 RedisTemplate 类型,不带 K V

@RestController
@RequestMapping("/dao")
public class DaoController {

    private static final Logger logger = LoggerFactory.getLogger(DaoController.class);

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

二,序列化与反序列化

参考

Redis使用中,V 需要序列化,即 implements Serializable,如果不实现这个接口,会报错:java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.peter.model.Employee]

import java.io.Serializable;

public class Employee implements Serializable {

redis默认使用JdkSerializationRedisSerializer来进行序列化,会造成key是乱码,解决办法是设置 RedisSerializer,参考RedisConfig

peteryuanpan commented 3 years ago

参考

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set:有序集合)

string 和 hash 的区别:string 是 string -> string 的键值对结构,hash 是 string -> map 的键值对结构

简单举个实例来描述下Hash的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息

id,name,age,sex

1、1,张三,16,1

2、2,李四,22,1

3、3,王五,28,0

4、4,赵六,32,1

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:

第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的

那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,也就是说,Key仍然是用户ID,value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field),也就是通过 key(用户ID) + field(属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题

使用hash结构可以如下存储上面的数据 hset user_1 id 1 name 张三 age 16 sex 1 hset user_2 id 2 name 李四 age 22 sex 1 hset user_3 id 3 name 王五 age 28 sex 0 hset user_4 id 4 name 赵六 age 32 sex 1

因此,大部分情况下,尤其是存储对象包含多个字段,且只要修改某个字段时,用hash合适;而需要将整个对象都序列化/反序列化时,或者对象包含字段不多时(数据量小),可以用string