写点什么

Spring Boot 集成 Redis 配置 MyBatis 二级缓存

  • 2022 年 10 月 01 日
    湖南
  • 本文字数:6162 字

    阅读完需:约 20 分钟

Spring Boot 集成 Redis 配置 MyBatis 二级缓存

写在前面

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


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

源码获取

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

GitHub

https://github.com/micromaples/my-project
复制代码

码云 Gitee

https://gitee.com/micromaple/my-project
复制代码

一、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内容如下:


version: '3.1'services:  redis:    image: redis:6.2.4    container_name: redis    restart: always    command: redis-server --requirepass 123456    ports:      - '6379:6379'    volumes:      - ./data:/data    environment:      TZ: Asia/Shanghai
复制代码


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



2.2、项目引入 Redis

2.2.1、Maven 依赖

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>  <groupId>org.apache.commons</groupId>  <artifactId>commons-pool2</artifactId></dependency>
复制代码


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

2.2.2、配置 application.yml

spring:  redis:    host: 192.168.110.158    port: 6379    password: 123456    lettuce:      pool:        #最大允许连接数        max-active: 100        #最小空闲连接数,最少准备5个可用连接在连接池候着        min-idle: 5        #最大空闲连接数,空闲连接超过10个后自动释放        max-idle: 10        #当连接池到达上限后,最多等待30秒尝试获取连接,超时报错        max-wait: 30000    timeout: 2000
复制代码

2.2.3、配置序列化规则

RedisTemplateConfiguration配置类如下:

package com.micromaple.my.project.server.config;
import com.fasterxml.jackson.annotation.JsonInclude;import com.fasterxml.jackson.databind.ObjectMapper;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.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;
/** * RedisTemplate配置 * Title: RedisTemplateConfiguration * Description: * * @author Micromaple */@Configurationpublic class RedisTemplateConfiguration { /** * redisTemplate * * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 使用Jackson2JsonRedisSerialize 替换默认序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); //对于Null值不输出 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 设置key和value的序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 设置hashKey和hashValue的序列化规则 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); //afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行,init - method 后执行。 redisTemplate.afterPropertiesSet(); return redisTemplate; }}
复制代码

三、配置二级缓存

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

本文以 Redis 为例。

2.1、开启二级缓存

application.yml 中增加如下配置:

# 开启MyBatis二级缓存mybatis:  configuration:    cache-enabled: true
复制代码


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

# MyBatis-Plus开启二级缓存mybatis-plus:  configuration:    cache-enabled: true
复制代码

2.2、自定义缓存类

MybatisRedisCache 缓存工具类如下:

package com.micromaple.my.project.server.utils;
import com.micromaple.my.project.server.config.ApplicationContextHolder;import org.apache.commons.collections.CollectionUtils;import org.apache.ibatis.cache.Cache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;
/** * MybatisRedisCache 缓存工具类 * Title: MybatisRedisCache * Description: * * @author Micromaple */public class MybatisRedisCache implements Cache { private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final String id; // cache instance id private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisRedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; }
@Override public String getId() { return id; }
/** * Put query result to redis * * @param key * @param value */ @Override public void putObject(Object key, Object value) { try { redisTemplate = getRedisTemplate(); if (value != null) { redisTemplate.opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); } logger.debug("Put query result to redis"); } catch (Throwable t) { logger.error("Redis put failed", t); }

}
/** * Get cached query result from redis * * @param key * @return */ @Override public Object getObject(Object key) { try { redisTemplate = getRedisTemplate(); logger.debug("Get cached query result from redis"); return redisTemplate.opsForValue().get(key.toString()); } catch (Throwable t) { logger.error("Redis get failed, fail over to db", t); return null; } }
/** * Remove cached query result from redis * * @param key * @return */ @Override @SuppressWarnings("unchecked") public Object removeObject(Object key) { try { redisTemplate = getRedisTemplate(); redisTemplate.delete(key.toString()); logger.debug("Remove cached query result from redis"); } catch (Throwable t) { logger.error("Redis remove failed", t); } return null; }
/** * Clears this cache instance */ @Override public void clear() { redisTemplate = getRedisTemplate(); Set<String> keys = redisTemplate.keys("*:" + this.id + "*"); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } logger.debug("Clear all the cached query result from redis"); }
/** * This method is not used * * @return */ @Override public int getSize() { return 0; }
@Override public ReadWriteLock getReadWriteLock() { return readWriteLock; }
private RedisTemplate getRedisTemplate() { if (redisTemplate == null) { redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; }}
复制代码


ApplicationContextHolder如下:

package com.micromaple.my.project.server.config;
import org.apache.commons.lang3.Validate;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.BeansException;import org.springframework.beans.factory.DisposableBean;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;
/** * Spring bean的工具类 * Title: ApplicationContextHolder * Description: * * @author Micromaple */@Componentpublic class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class);
private static ApplicationContext applicationContext;
/** * 获取存储在静态变量中的 ApplicationContext * * @return */ public static ApplicationContext getApplicationContext() { assertContextInjected(); return applicationContext; }
/** * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型 * * @param name * @param <T> * @return */ public static <T> T getBean(String name) { assertContextInjected(); return (T) applicationContext.getBean(name); }
/** * 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型 * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { assertContextInjected(); return applicationContext.getBean(clazz); }
/** * 实现 DisposableBean 接口,在 Context 关闭时清理静态变量 * * @throws Exception */ public void destroy() throws Exception { logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext); applicationContext = null; }
/** * 实现 ApplicationContextAware 接口,注入 Context 到静态变量中 * * @param applicationContext * @throws BeansException */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextHolder.applicationContext = applicationContext; }
/** * 断言 Context 已经注入 */ private static void assertContextInjected() { Validate.validState(applicationContext != null, "applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext"); }}
复制代码

2.3、增加注解

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

package com.micromaple.my.project.server.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.micromaple.my.project.server.domain.SysUser;import com.micromaple.my.project.server.utils.MybatisRedisCache;import org.apache.ibatis.annotations.CacheNamespace;
/** * <p> * 用户表 Mapper 接口 * </p> * * @author Micromaple * @since 2022-09-21 21:51:15 */@CacheNamespace(implementation = MybatisRedisCache.class)public interface SysUserMapper extends BaseMapper<SysUser> {
}
复制代码

2.4、测试验证

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

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


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


发布于: 刚刚阅读数: 4
用户头像

InfoQ签约作者,公众号:程序员微枫 2020.11.11 加入

东隅已逝,桑榆非晚!悟已往之不谏,知来者之可追!

评论

发布
暂无评论
Spring Boot 集成 Redis 配置 MyBatis 二级缓存_redis_微枫Micromaple_InfoQ写作社区