likes
comments
collection
share

SpringBoot +Mybatis + Redis实现缓存(案例解析)

作者站长头像
站长
· 阅读数 53

大家好,我是卷心菜。本篇主要讲解用Redis实现缓存的案例解析,如果您看完文章有所收获,可以三连支持博主哦~,嘻嘻。


一、前言

  • 各位小伙伴们,博主写的Redis专栏有一段时间了,前面讲解了Redis的例如五种常用数据类型、redis实现持久化、主从复制、哨兵模式等等理论知识。接下来,就要开始着重讲解用redis实现各种业务的代码分析和模拟,一起学习吧!

二、数据库表

CREATE TABLE `users` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(50) NOT NULL DEFAULT '' COMMENT '密码',
  `sex` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
  `deleted` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8 COMMENT='用户表';

类型介绍:

  • unsigned:默认的 int 类型,取值范围是 -21474836482147483647 之间,而 unsigned 的取值范围是 04294967295 之间。默认的 int 类型,允许插入负数,unsigned 设置后,无法插入负数。
  • tinyint:独立使用时,使用范围是0-127。tinyint unsigned的使用范围是0~255的整型数据,存储大小为1字节。如果数字较小比如用0和1表示性别或者表示年龄时,可以用tinyint。
  • timestamp:DEFAULT CURRENT_TIMESTAMP,如插入记录时未指定具体时间数据则将该时间戳字段值设置为当前时间;更新记录时,时间戳字段包含ON UPDATE CURRENT_TIMESTAMP,如更新记录时未指定具体时间数据则将该时间戳字段值设置为当前时间

三、配置文件

spring.application.name=spring-boot-mybatis-redis
server.port=8080
# mybatis配置
mybatis.mapper-locations=classpath*:com/cabbage/redis/mapper/xml/*.xml
# 数据库配置
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_redis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=0000
# 日志配置
logging.level.com.cabbage=debug
# swagger2配置
spring.swagger2.enabled=true
# redis配置
spring.redis.database=0
spring.redis.host=192.18.5.131
spring.redis.port=6379
spring.redis.password=123

四、配置类

swagger2配置类:

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Value(value = "${spring.swagger2.enabled}")
    private Boolean swaggerEnabled;
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(swaggerEnabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.cabbage.redis"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("接口文档")
                .description("Spring Boot - redis")
                .termsOfServiceUrl("https://cabbage.blog.csdn.net/")
                .version("1.0")
                .build();
    }
}

redis序列化配置类:

@Configuration
public class RedisConfiguration {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //创建一个json的序列化对象
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置hash key序列化方式string
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //设置hash value的序列化方式json
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

注意:

/**
 * 重写Redis序列化方式,使用Json方式:
 * 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到Redis的。
 * RedisTemplate默认使用的是JdkSerializationRedisSerializer,
 * StringRedisTemplate默认使用的是StringRedisSerializer
 * Spring Data JPA为我们提供了下面的Serializer:
 * GenericToStringSerializer、Jackson2JsonRedisSerializer、
 * JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、
 * OxmSerializer、StringRedisSerializer。
 * 在此我们将自己配置RedisTemplate并定义Serializer。
 */

五、实体类设计

@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(generator = "JDBC")
    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;
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}
  • @Id:建议有一个@Id注解作为主键的字段,可以有多个@Id注解的字段作为联合主键;默认情况下,实体类中如果不存在包含@Id注解的字段,所有的字段都会作为主键字段进行使用(这种效率极低)
  • @GeneratedValue(generator = "JDBC"):这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键

六、核心代码

@Service
public class UserService {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    # redis数据库key的前缀
    public static final String CACHE_KEY_USER = "user:";
    
    # 把数据先放入数据库,然后再放入Redis数据中
    public void createUser(User obj) {
        this.userMapper.insertSelective(obj);
        //缓存key
        String key = CACHE_KEY_USER + obj.getId();
        //到数据库里面,重新捞出新数据出来,做缓存
        obj = this.userMapper.selectByPrimaryKey(obj.getId());
        //opsForValue代表了Redis的String数据结构
        //set代表了redis的SET命令
        redisTemplate.opsForValue().set(key, obj);
    }
    
    # 查找数据,实现缓存
    public User findUserById(Integer userid) {
        ValueOperations<String, User> operations = redisTemplate.opsForValue();
        //缓存key
        String key = CACHE_KEY_USER + userid;
        //1.先去redis查 ,如果查到直接返回,没有的话直接去数据库捞
        //Redis 用了GET命令
        User user = operations.get(key);
        //2.redis没有的话,直接去数据库捞
        if (user == null) {
            user = this.userMapper.selectByPrimaryKey(userid);
            //由于redis没有才到数据库捞,所以必须把捞到的数据写入redis,方便下次查询能redis命中。
            operations.set(key, user);
        }
        return user;
    }
    
    public void updateUser(User obj) {
        //1.先直接修改数据库
        this.userMapper.updateByPrimaryKeySelective(obj);
        //2.再修改缓存
        //缓存key
        String key = CACHE_KEY_USER + obj.getId();
        obj = this.userMapper.selectByPrimaryKey(obj.getId());
        //修改也是用SET命令,重新设置,Redis 没有update操作,都是重新设置新值
        redisTemplate.opsForValue().set(key, obj);
    }
}

注意:

  • createUser方法中语句:obj = this.userMapper.selectByPrimaryKey(obj.getId());一定要有!是因为参数obj是一个对象,没有具体的时间戳;把obj存入mysql后就有了时间戳,然后才能从数据库中拿出数据,变成JSON格式后存入redis。

  • selectByPrimaryKey():通过主键id查找对象

  • insertSelective():只给有值的字段赋值(会对传进来的值做非空判断)

  • insert():所有的字段都会添加一遍即使没有值

  • updateByPrimaryKeySelective():不会把null值插入数据库,避免覆盖之前有值的。

  • updateByPrimaryKey():会根据传入的对象,全部取值插入数据库,会存在覆盖数据的问题。具体使用哪个函数看场景。


感谢阅读,一起进步,嘻嘻~

转载自:https://juejin.cn/post/7132828299843076104
评论
请登录