Redis高级(二)、Springboot+Mybatis+redis(重写Redis序列化)缓存实战

57 阅读5分钟

觉得对你有益的小伙伴记得点个赞+关注

后续完整内容持续更新中

希望一起交流的欢迎发邮件至javalyhn@163.com

学Redis,光看理论未免太枯燥了。因此先来一个小实战热热身。 该实战用到了Swagger,一个很简单的工具,不会没关系,看完下面的实战就会用了,要是想更加深入的理解,欢迎期待我来更新。

1 Springboot+Mybatis+redis缓存实战

1.1 创建Moudle

image.png

1.2 改pom

<dependencies>
    <!--SpringBoot通用依赖模块-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--swagger2-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
    <!--SpringBoot与Redis整合依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>


    <!-- jedis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <!--Mysql数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
    <!--SpringBoot集成druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>${druid.version}</version>
    </dependency>
    <!--mybatis和springboot整合-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.spring.boot.version}</version>
    </dependency>
    <!-- 添加springboot对amqp的支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.10</version>
    </dependency>
    <!--通用基础配置junit/devtools/test/log4j/lombok/hutool-->
    <!--hutool-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.2.3</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
    <!--persistence-->
    <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0.2</version>
    </dependency>
    <!--通用Mapper-->
    <dependency>
        <groupId>tk.mybatis</groupId>
        <artifactId>mapper</artifactId>
        <version>${mapper.version}</version>
    </dependency>
</dependencies>

1.3 写yml(properties)

server.port=5555

spring.application.name=redis

  # ========================logging 日志相关的配置=====================
  #系统默认,全局root配置的日志形式,可以注释掉
logging.level.root=warn
  #开发人员自己设置的包结构,对那个package进行什么级别的日志监控
logging.level.com.lyhn.redis=info
  #开发人员自定义日志路径和日志名称
logging.file.name=D:/mylogs2/logs/redis.log
  #%d{HH:mm:ss.SSS}――日志输出时间
  #%thread――输出日志的进程名字,这在Web应用以及异步任务处理中很有用
  #%-5level――日志级别,并且使用5个字符靠左对齐
  #%logger- ――日志输出者的名字
  #%msg――日志消息
  #%n――平台的换行符
  #logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n

  # ========================alibaba.druid相关配置=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db2022?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false

  # ========================redis相关配置=====================
  # Redis数据库索引(默认为0)
spring.redis.database=0
  # Redis服务器地址
spring.redis.host=
  # Redis服务器连接端口
spring.redis.port=
  # Redis服务器连接密码(默认为空)
spring.redis.password=
  # 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1,记得加入单位ms,不然idea报红色
spring.redis.lettuce.pool.max-wait=-1ms
  # 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
  # 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

  # ========================mybatis相关配置===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.lyhn.redis.entities

  # ========================swagger=====================
spring.swagger2.enabled=true

1.4 主启动

@SpringBootApplication
@MapperScan("com.lyhn.redis_practice.mapper") //import tk.mybatis.spring.annotation.MapperScan;
public class RedisPracticeApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisPracticeApplication.class, args);
    }

}

1.5 业务类

RedisConfig

@Configuration
public class RedisConfig
{
    /**
     * @param lettuceConnectionFactory
     * @return
     *
     * redis序列化的工具配置类,下面这个请一定开启配置
     * 127.0.0.1:6379> keys *
     * 1) "ord:102"  序列化过
     * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
     */
    @Bean
    public RedisTemplate<String,Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
    {
        RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

编写这个配置类的原因是为了解决Redis序列化的问题

如果我们不进行序列化

image.png

正确情况

image.png

SwaggerConfig

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Value("${spring.swagger2.enabled}")
    private Boolean enabled;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.lyhn.redis_practice")) //你自己的package
                .paths(PathSelectors.any())
                .build();
    }
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("springboot利用swagger2构建api接口文档 "+"\t"+new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .description("lyhn的高级篇redis")
                .version("1.0")
                .build();
    }

}

1.6 干活

  1. 检查数据库表t_user是否ok
  2. TK Mapper拷贝来的User juejin.cn/post/719060…
@Table(name = "t_user")
public class User {
    @GeneratedValue(generator = "JDBC")
    @Id
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 性别 0=女 1=男 
     */
    private Byte sex;

    /**
     * 删除标志,默认0不删除,1删除
     */
    private Byte deleted;

    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private Date updateTime;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取用户名
     *
     * @return username - 用户名
     */
    public String getUsername() {
        return username;
    }

    /**
     * 设置用户名
     *
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别 0=女 1=男 
     *
     * @return sex - 性别 0=女 1=男 
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * 设置性别 0=女 1=男 
     *
     * @param sex 性别 0=女 1=男 
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取更新时间
     *
     * @return update_time - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
     * 获取创建时间
     *
     * @return create_time - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}
  1. Swagger和前台DTO配置UserDTO(DTO:data tranfer object 数据传输对象)
@NoArgsConstructor
@AllArgsConstructor
@Data
@ApiModel(value = "用户信息")
public class UserDTO implements Serializable
{
    @ApiModelProperty(value = "用户ID")
    private Integer id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "性别 0=女 1=男 ")
    private Byte sex;

    @ApiModelProperty(value = "删除标志,默认0不删除,1删除")
    private Byte deleted;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取用户名
     *
     * @return username - 用户名
     */
    public String getUsername() {
        return username;
    }

    /**
     * 设置用户名
     *
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别 0=女 1=男 
     *
     * @return sex - 性别 0=女 1=男 
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * 设置性别 0=女 1=男 
     *
     * @param sex 性别 0=女 1=男 
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取更新时间
     *
     * @return update_time - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
     * 获取创建时间
     *
     * @return create_time - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}
  1. mapper接口
public interface UserMapper extends Mapper<User> {
}
  1. mapperSQL文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.redis.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.atguigu.redis.entities.User">
    <!--
      WARNING - @mbg.generated
    -->
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="sex" jdbcType="TINYINT" property="sex" />
    <result column="deleted" jdbcType="TINYINT" property="deleted" />
    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
  </resultMap>
</mapper>
  1. service类
@Slf4j
@Service
public class UserService {
    public static final String CACHE_KEY_USER = "user:";

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private UserMapper userMapper;

    public void addUser(User user){
        log.info("插入的user:{}",user);
        int i = userMapper.insertSelective(user);//insertSelective只会插入不为null的数据
        log.info("插入后的user:{}",user);

        log.info("==========");
        System.out.println();

        if(i > 0) {
            //到数据库里面,重新取出来再放进缓存  (mysql与redis要保持数据一致性 防止网络抖动等原因 要使得数据插入数据库一定成功)
            user = userMapper.selectByPrimaryKey(user.getId());
            //缓存key
            String key = CACHE_KEY_USER + user.getId();
            //往mysql里面插入成功后再从mysql查询出来,再插入redis
            redisTemplate.opsForValue().set(key,user);
        }
    }

    public void deleteUser(Integer id) {
        //1 先直接删除数据库
        int i = userMapper.deleteByPrimaryKey(id);
        if(i > 0) {
            //2 再修改缓存
            String key = CACHE_KEY_USER + id;
            redisTemplate.delete(key);
        }
    }

    public void updateUser(User user) {
        //1 先直接修改数据库
        int i = userMapper.updateByPrimaryKeySelective(user);
        if(i > 0) {
            //2 再修改缓存
            //缓存key
            String key = CACHE_KEY_USER + user.getId();
            user = userMapper.selectByPrimaryKey(user.getId());
            //修改也是用set命令,重新设置,Redis没有update操作,都是重新设置新值
            redisTemplate.opsForValue().set(key,user);
        }
    }

    /**
     * 先去redis里面找数据 ,找到就直接返回,找不到再去查询mysql
     * @param userId
     * @return
     */
    public User findUserById(Integer userId){
        User user = null;

        //缓存key
        String key=CACHE_KEY_USER+userId;
        //1 查询redis
        user = (User) redisTemplate.opsForValue().get(key);

        //2 redis无,进一步查询mysql
        if(user==null)
        {
            //3 从mysql查出来user
            user=this.userMapper.selectByPrimaryKey(userId);
            // mysql有,redis无
            if (user != null) {
                //4 把mysql捞到的数据写入redis,方便下次查询能redis命中。
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }

}
  1. controller
@RestController
@Slf4j
@Api(description = "用户User接口")
public class UserController {
    @Resource
    private UserService userService;

    @ApiOperation("数据库初始化五条数据")
    @PostMapping(value = "user/add")
    public void addUser() {
        for (int i = 0; i < 5; i++) {
            User user = new User();
            user.setUsername("lyhn" + i);
            user.setPassword(IdUtil.simpleUUID().substring(0,6));
            user.setSex((byte) new Random().nextInt(2));

            userService.addUser(user);
        }
    }

    @ApiOperation("删除某条数据")
    @RequestMapping(value = "/user/del/{id}", method = RequestMethod.POST)
    public void deleteUser(@PathVariable Integer id) {
        userService.deleteUser(id);
    }

    @ApiOperation("修改某条数据")
    @RequestMapping(value = "/user/update", method = RequestMethod.POST)
    public void updateUser(@RequestBody User userDTO) {
        User user = new User();
        BeanUtils.copyProperties(userDTO, user);
        userService.updateUser(user);
    }

    @ApiOperation("单个用户查询,按userid查用户信息")
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public User findUserById(@PathVariable int id) {
        return this.userService.findUserById(id);
    }
}
  1. 启动测试Swagger是否OK

http://localhost:你的微服务端口/swagger-ui.html#/

image.png

演示一下查询操作(注意此事没有任何数据)

image.png

演示一下插入操作

image.png

查看Redis数据

image.png

剩下的可以小伙伴们自己写完了去看,很简单的

那么这个实战我们就做完了,后面还有很多案例,这个很简单,只是个开胃菜,欢迎期待下次更新!!!!