SpringBoot整合Redis配置MyBatis二级缓存

叁歲伎倆 2023-09-26 09:42 128阅读 0赞

目录

    • 写在前面
    • 源码获取
    • 一、MyBatis缓存机制
      • 1.1、一级缓存
      • 1.2、二级缓存
    • 二、集成Redis
      • 2.1、安装Redis
      • 2.2、项目引入Redis
        • 2.2.1、Maven依赖
        • 2.2.2、配置application.yml
        • 2.2.3、配置序列化规则
    • 三、配置二级缓存
      • 2.1、开启二级缓存
      • 2.2、自定义缓存类
      • 2.3、增加注解
      • 2.4、测试验证

写在前面

文中项目基于从0到1项目搭建-框架搭建,如果你是新手,可以跟着上期内容先动手把项目框架搭建起来,然后在结合本期内容继续深入学习,这样会有更好的效果。

接下来正式介绍本文,本文讲的是在 Spring Boot 项目中集成使用 Redis,并使用 Redis 实现 MyBatis 的二级缓存。使用场景就是在高并发的环境下,大量的查询直接落入DB,会导致数据库宕机,从而导致服务雪崩的情况。我们使用Redis作为MyBatis二级缓存,可以充分的缓解数据库的压力,从而达到服务的高可用。

源码获取

源码在 GitCodeGitHub 以及 码云,持续更新中,别忘了 star 喔~

GitCode

  1. https://gitcode.net/qq_41779565/my-project.git

GitHub

  1. https://github.com/micromaples/my-project

码云Gitee

  1. https://gitee.com/micromaple/my-project

如果不会使用 Git 的小伙伴,我已经上传到了CSDN,资源下载传送门,有会员的小伙伴直接下载即可,没有会员的小伙伴私聊我Mybatis二级缓存可直接获取

一、MyBatis缓存机制

Mybatis 提供了查询缓存来缓存数据,以提高查询效率。缓存级别分为一级缓存二级缓存

1.1、一级缓存

一级缓存为 SqlSession 级别的缓存,也就是会话级缓存,是基于HashMap的本地缓存,当同一个SqlSession执行两次相同的SQL语句时,第一次执行完后会将数据库中查询到的结果写到缓存,第二次查询时直接从缓存中读取,不经过数据库了。一级缓存默认是开启的。

1.2、二级缓存

二级缓存为mapper级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 去操作数据库得到数据会存在二级缓存区域,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。其作用域是 mapper 的同一个 namespace,不同的 sqlSession 两次执行相同 namespace下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。

二、集成Redis

2.1、安装Redis

使用Docker Compose 安装Redis。docker-compose.yml内容如下:

  1. version: '3.1'
  2. services:
  3. redis:
  4. image: redis:6.2.4
  5. container_name: redis
  6. restart: always
  7. command: redis-server --requirepass 123456
  8. ports:
  9. - '6379:6379'
  10. volumes:
  11. - ./data:/data
  12. environment:
  13. TZ: Asia/Shanghai

安装启动完成后,可使用Redis连接工具测试

在这里插入图片描述

2.2、项目引入Redis

2.2.1、Maven依赖
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.apache.commons</groupId>
  7. <artifactId>commons-pool2</artifactId>
  8. </dependency>

额外引入commons-pool2是因为data-redis底层Redis连接池基于apache commons-pool2开 发,不加入依赖会报ClassNotFoundException

2.2.2、配置application.yml
  1. spring:
  2. redis:
  3. host: 192.168.110.158
  4. port: 6379
  5. password: 123456
  6. lettuce:
  7. pool:
  8. #最大允许连接数
  9. max-active: 100
  10. #最小空闲连接数,最少准备5个可用连接在连接池候着
  11. min-idle: 5
  12. #最大空闲连接数,空闲连接超过10个后自动释放
  13. max-idle: 10
  14. #当连接池到达上限后,最多等待30秒尝试获取连接,超时报错
  15. max-wait: 30000
  16. timeout: 2000
2.2.3、配置序列化规则

RedisTemplateConfiguration配置类如下:

  1. package com.micromaple.my.project.server.config;
  2. import com.fasterxml.jackson.annotation.JsonInclude;
  3. import com.fasterxml.jackson.databind.ObjectMapper;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.data.redis.connection.RedisConnectionFactory;
  7. import org.springframework.data.redis.core.RedisTemplate;
  8. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  9. import org.springframework.data.redis.serializer.StringRedisSerializer;
  10. /**
  11. * RedisTemplate配置
  12. * Title: RedisTemplateConfiguration
  13. * Description:
  14. *
  15. * @author Micromaple
  16. */
  17. @Configuration
  18. public class RedisTemplateConfiguration {
  19. /**
  20. * redisTemplate
  21. *
  22. * @param redisConnectionFactory
  23. * @return
  24. */
  25. @Bean
  26. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory
  27. redisConnectionFactory) {
  28. RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
  29. redisTemplate.setConnectionFactory(redisConnectionFactory);
  30. // 使用Jackson2JsonRedisSerialize 替换默认序列化
  31. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  32. ObjectMapper objectMapper = new ObjectMapper();
  33. //对于Null值不输出
  34. objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
  35. jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
  36. // 设置key和value的序列化规则
  37. redisTemplate.setKeySerializer(new StringRedisSerializer());
  38. redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  39. // 设置hashKey和hashValue的序列化规则
  40. redisTemplate.setHashKeySerializer(new StringRedisSerializer());
  41. redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  42. //afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行,init - method 后执行。
  43. redisTemplate.afterPropertiesSet();
  44. return redisTemplate;
  45. }
  46. }

三、配置二级缓存

配置实现MyBatis二级缓存的方式有多种,比如:EhCacheJBossCacheRedis,其核心原理就是客户端实现 MyBatis 提供的Cache 接口,并重写其中的方法,达到二级缓存的效果。

本文以 Redis 为例。

2.1、开启二级缓存

application.yml 中增加如下配置:

  1. # 开启MyBatis二级缓存
  2. mybatis:
  3. configuration:
  4. cache-enabled: true

如果使用的是 MyBatis-Plus ,则使用如下配置:

  1. # MyBatis-Plus开启二级缓存
  2. mybatis-plus:
  3. configuration:
  4. cache-enabled: true

2.2、自定义缓存类

MybatisRedisCache 缓存工具类如下:

  1. package com.micromaple.my.project.server.utils;
  2. import com.micromaple.my.project.server.config.ApplicationContextHolder;
  3. import org.apache.commons.collections.CollectionUtils;
  4. import org.apache.ibatis.cache.Cache;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.data.redis.core.RedisTemplate;
  8. import java.util.Set;
  9. import java.util.concurrent.TimeUnit;
  10. import java.util.concurrent.locks.ReadWriteLock;
  11. import java.util.concurrent.locks.ReentrantReadWriteLock;
  12. /**
  13. * MybatisRedisCache 缓存工具类
  14. * Title: MybatisRedisCache
  15. * Description:
  16. *
  17. * @author Micromaple
  18. */
  19. public class MybatisRedisCache implements Cache {
  20. private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
  21. private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  22. private final String id; // cache instance id
  23. private RedisTemplate redisTemplate;
  24. private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
  25. public MybatisRedisCache(String id) {
  26. if (id == null) {
  27. throw new IllegalArgumentException("Cache instances require an ID");
  28. }
  29. this.id = id;
  30. }
  31. @Override
  32. public String getId() {
  33. return id;
  34. }
  35. /**
  36. * Put query result to redis
  37. *
  38. * @param key
  39. * @param value
  40. */
  41. @Override
  42. public void putObject(Object key, Object value) {
  43. try {
  44. redisTemplate = getRedisTemplate();
  45. if (value != null) {
  46. redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
  47. }
  48. logger.debug("Put query result to redis");
  49. } catch (Throwable t) {
  50. logger.error("Redis put failed", t);
  51. }
  52. }
  53. /**
  54. * Get cached query result from redis
  55. *
  56. * @param key
  57. * @return
  58. */
  59. @Override
  60. public Object getObject(Object key) {
  61. try {
  62. redisTemplate = getRedisTemplate();
  63. logger.debug("Get cached query result from redis");
  64. return redisTemplate.opsForValue().get(key.toString());
  65. } catch (Throwable t) {
  66. logger.error("Redis get failed, fail over to db", t);
  67. return null;
  68. }
  69. }
  70. /**
  71. * Remove cached query result from redis
  72. *
  73. * @param key
  74. * @return
  75. */
  76. @Override
  77. @SuppressWarnings("unchecked")
  78. public Object removeObject(Object key) {
  79. try {
  80. redisTemplate = getRedisTemplate();
  81. redisTemplate.delete(key.toString());
  82. logger.debug("Remove cached query result from redis");
  83. } catch (Throwable t) {
  84. logger.error("Redis remove failed", t);
  85. }
  86. return null;
  87. }
  88. /**
  89. * Clears this cache instance
  90. */
  91. @Override
  92. public void clear() {
  93. redisTemplate = getRedisTemplate();
  94. Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
  95. if (!CollectionUtils.isEmpty(keys)) {
  96. redisTemplate.delete(keys);
  97. }
  98. logger.debug("Clear all the cached query result from redis");
  99. }
  100. /**
  101. * This method is not used
  102. *
  103. * @return
  104. */
  105. @Override
  106. public int getSize() {
  107. return 0;
  108. }
  109. @Override
  110. public ReadWriteLock getReadWriteLock() {
  111. return readWriteLock;
  112. }
  113. private RedisTemplate getRedisTemplate() {
  114. if (redisTemplate == null) {
  115. redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
  116. }
  117. return redisTemplate;
  118. }
  119. }

ApplicationContextHolder如下:

  1. package com.micromaple.my.project.server.config;
  2. import org.apache.commons.lang3.Validate;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.beans.BeansException;
  6. import org.springframework.beans.factory.DisposableBean;
  7. import org.springframework.context.ApplicationContext;
  8. import org.springframework.context.ApplicationContextAware;
  9. import org.springframework.stereotype.Component;
  10. /**
  11. * Spring bean的工具类
  12. * Title: ApplicationContextHolder
  13. * Description:
  14. *
  15. * @author Micromaple
  16. */
  17. @Component
  18. public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {
  19. private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class);
  20. private static ApplicationContext applicationContext;
  21. /**
  22. * 获取存储在静态变量中的 ApplicationContext
  23. *
  24. * @return
  25. */
  26. public static ApplicationContext getApplicationContext() {
  27. assertContextInjected();
  28. return applicationContext;
  29. }
  30. /**
  31. * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
  32. *
  33. * @param name
  34. * @param <T>
  35. * @return
  36. */
  37. public static <T> T getBean(String name) {
  38. assertContextInjected();
  39. return (T) applicationContext.getBean(name);
  40. }
  41. /**
  42. * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
  43. *
  44. * @param clazz
  45. * @param <T>
  46. * @return
  47. */
  48. public static <T> T getBean(Class<T> clazz) {
  49. assertContextInjected();
  50. return applicationContext.getBean(clazz);
  51. }
  52. /**
  53. * 实现 DisposableBean 接口,在 Context 关闭时清理静态变量
  54. *
  55. * @throws Exception
  56. */
  57. public void destroy() throws Exception {
  58. logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext);
  59. applicationContext = null;
  60. }
  61. /**
  62. * 实现 ApplicationContextAware 接口,注入 Context 到静态变量中
  63. *
  64. * @param applicationContext
  65. * @throws BeansException
  66. */
  67. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  68. ApplicationContextHolder.applicationContext = applicationContext;
  69. }
  70. /**
  71. * 断言 Context 已经注入
  72. */
  73. private static void assertContextInjected() {
  74. Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext");
  75. }
  76. }

2.3、增加注解

在 Mapper 接口中增加 @CacheNamespace(implementation = MybatisRedisCache.class) 注解,声明需要使用二级缓存。

  1. package com.micromaple.my.project.server.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.micromaple.my.project.server.domain.SysUser;
  4. import com.micromaple.my.project.server.utils.MybatisRedisCache;
  5. import org.apache.ibatis.annotations.CacheNamespace;
  6. /**
  7. * <p>
  8. * 用户表 Mapper 接口
  9. * </p>
  10. *
  11. * @author Micromaple
  12. * @since 2022-09-21 21:51:15
  13. */
  14. @CacheNamespace(implementation = MybatisRedisCache.class)
  15. public interface SysUserMapper extends BaseMapper<SysUser> {
  16. }

2.4、测试验证

访问查询所有用户接口http://localhost:8899/sys-user/get/all

访问完成后,我们打开Redis查询工具,可以看到已经将我们查询出来的数据缓存起来了。效果图如下:
在这里插入图片描述

接着,我们再次访问查询所有用户接口,我们可以在控制台日志中看到,第二次查询并没有走数据库,而是直接在Redis中取出来了

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,128人围观)

还没有评论,来说两句吧...

相关阅读

    相关 mybatis二级缓存配置

    一、应用场景 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sq