目录
写在前面
文中项目基于从0到1项目搭建-框架搭建,如果你是新手,可以跟着上期内容先动手把项目框架搭建起来,然后在结合本期内容继续深入学习,这样会有更好的效果。
接下来正式介绍本文,本文讲的是在 Spring Boot 项目中集成使用 Redis,并使用 Redis 实现 MyBatis 的二级缓存。使用场景就是在高并发的环境下,大量的查询直接落入DB,会导致数据库宕机,从而导致服务雪崩的情况。我们使用Redis作为MyBatis二级缓存,可以充分的缓解数据库的压力,从而达到服务的高可用。
源码获取
源码在
GitCode
、
GitHub
以及
码云
,持续更新中,别忘了
star
喔~
GitCode
https://gitcode.net/qq_41779565/my-project.git
GitHub
https://github.com/micromaples/my-project
码云Gitee
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
内容如下:
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:6379password:123456lettuce:pool:#最大允许连接数max-active:100#最小空闲连接数,最少准备5个可用连接在连接池候着min-idle:5#最大空闲连接数,空闲连接超过10个后自动释放max-idle:10#当连接池到达上限后,最多等待30秒尝试获取连接,超时报错max-wait:30000timeout:2000
2.2.3、配置序列化规则
RedisTemplateConfiguration
配置类如下:
packagecom.micromaple.my.project.server.config;importcom.fasterxml.jackson.annotation.JsonInclude;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;/**
* RedisTemplate配置
* Title: RedisTemplateConfiguration
* Description:
*
* @author Micromaple
*/@ConfigurationpublicclassRedisTemplateConfiguration{/**
* redisTemplate
*
* @param redisConnectionFactory
* @return
*/@BeanpublicRedisTemplate<Object,Object>redisTemplate(RedisConnectionFactory
redisConnectionFactory){RedisTemplate<Object,Object> redisTemplate =newRedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);// 使用Jackson2JsonRedisSerialize 替换默认序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper =newObjectMapper();//对于Null值不输出
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key和value的序列化规则
redisTemplate.setKeySerializer(newStringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 设置hashKey和hashValue的序列化规则
redisTemplate.setHashKeySerializer(newStringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);//afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行,init - method 后执行。
redisTemplate.afterPropertiesSet();return redisTemplate;}}
三、配置二级缓存
配置实现MyBatis二级缓存的方式有多种,比如:
EhCache
、
JBossCache
、
Redis
,其核心原理就是客户端实现 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
缓存工具类如下:
packagecom.micromaple.my.project.server.utils;importcom.micromaple.my.project.server.config.ApplicationContextHolder;importorg.apache.commons.collections.CollectionUtils;importorg.apache.ibatis.cache.Cache;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.data.redis.core.RedisTemplate;importjava.util.Set;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.ReadWriteLock;importjava.util.concurrent.locks.ReentrantReadWriteLock;/**
* MybatisRedisCache 缓存工具类
* Title: MybatisRedisCache
* Description:
*
* @author Micromaple
*/publicclassMybatisRedisCacheimplementsCache{privatestaticfinalLogger logger =LoggerFactory.getLogger(MybatisRedisCache.class);privatefinalReadWriteLock readWriteLock =newReentrantReadWriteLock();privatefinalString id;// cache instance idprivateRedisTemplate redisTemplate;privatestaticfinallong EXPIRE_TIME_IN_MINUTES =30;// redis过期时间publicMybatisRedisCache(String id){if(id ==null){thrownewIllegalArgumentException("Cache instances require an ID");}this.id = id;}@OverridepublicStringgetId(){return id;}/**
* Put query result to redis
*
* @param key
* @param value
*/@OverridepublicvoidputObject(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
*/@OverridepublicObjectgetObject(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);returnnull;}}/**
* Remove cached query result from redis
*
* @param key
* @return
*/@Override@SuppressWarnings("unchecked")publicObjectremoveObject(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);}returnnull;}/**
* Clears this cache instance
*/@Overridepublicvoidclear(){
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
*/@OverridepublicintgetSize(){return0;}@OverridepublicReadWriteLockgetReadWriteLock(){return readWriteLock;}privateRedisTemplategetRedisTemplate(){if(redisTemplate ==null){
redisTemplate =ApplicationContextHolder.getBean("redisTemplate");}return redisTemplate;}}
ApplicationContextHolder
如下:
packagecom.micromaple.my.project.server.config;importorg.apache.commons.lang3.Validate;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.DisposableBean;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.stereotype.Component;/**
* Spring bean的工具类
* Title: ApplicationContextHolder
* Description:
*
* @author Micromaple
*/@ComponentpublicclassApplicationContextHolderimplementsApplicationContextAware,DisposableBean{privatestaticfinalLogger logger =LoggerFactory.getLogger(ApplicationContextHolder.class);privatestaticApplicationContext applicationContext;/**
* 获取存储在静态变量中的 ApplicationContext
*
* @return
*/publicstaticApplicationContextgetApplicationContext(){assertContextInjected();return applicationContext;}/**
* 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
*
* @param name
* @param <T>
* @return
*/publicstatic<T>TgetBean(String name){assertContextInjected();return(T) applicationContext.getBean(name);}/**
* 从静态变量 applicationContext 中获取 Bean,自动转型成所赋值对象的类型
*
* @param clazz
* @param <T>
* @return
*/publicstatic<T>TgetBean(Class<T> clazz){assertContextInjected();return applicationContext.getBean(clazz);}/**
* 实现 DisposableBean 接口,在 Context 关闭时清理静态变量
*
* @throws Exception
*/publicvoiddestroy()throwsException{
logger.debug("清除 SpringContext 中的 ApplicationContext: {}", applicationContext);
applicationContext =null;}/**
* 实现 ApplicationContextAware 接口,注入 Context 到静态变量中
*
* @param applicationContext
* @throws BeansException
*/publicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{ApplicationContextHolder.applicationContext = applicationContext;}/**
* 断言 Context 已经注入
*/privatestaticvoidassertContextInjected(){Validate.validState(applicationContext !=null,"applicationContext 属性未注入,请在 spring-context.xml 配置中定义 SpringContext");}}
2.3、增加注解
在 Mapper 接口中增加
@CacheNamespace(implementation = MybatisRedisCache.class)
注解,声明需要使用二级缓存。
packagecom.micromaple.my.project.server.mapper;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.micromaple.my.project.server.domain.SysUser;importcom.micromaple.my.project.server.utils.MybatisRedisCache;importorg.apache.ibatis.annotations.CacheNamespace;/**
* <p>
* 用户表 Mapper 接口
* </p>
*
* @author Micromaple
* @since 2022-09-21 21:51:15
*/@CacheNamespace(implementation =MybatisRedisCache.class)publicinterfaceSysUserMapperextendsBaseMapper<SysUser>{}
2.4、测试验证
访问查询所有用户接口
http://localhost:8899/sys-user/get/all
访问完成后,我们打开Redis查询工具,可以看到已经将我们查询出来的数据缓存起来了。效果图如下:
接着,我们再次访问查询所有用户接口,我们可以在控制台日志中看到,第二次查询并没有走数据库,而是直接在Redis中取出来了
版权归原作者 微枫Micromaple 所有, 如有侵权,请联系我们删除。