目录
设置RedisTemplate,在这可以对Redis的存储类型进行序列化,但是这里我没有使用
创建MybatisRedisCache类重写Mybatis二级缓存的Cache接口的实现
最后我们只需要在Mapper接口上添加@CacheNamespace注解,就完成了
一、Mybatis的缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
因为在Spring与Mybatis整合后,Mybatis的一级缓存是不能使用的,所以我们一般实现Mybatis的二级缓存,而在集群环境下,Mybatis的二级缓存只能实现单个节点的缓存,所以我们采用分布式的二级缓存功,这里使用的是Redis的实现。
二、使用Redis实现Mybatis二级缓存
首先先搭建一个spring boot项目,将依赖引入,我创建的是多项目,在父模块做了版本管理,这里继承父模块的版本,你们可按照需要引入自己的版本,要是项目不会创建可以参考我的另一篇文章https://blog.csdn.net/Cd_Air/article/details/118895785
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
配置application.yml文件
#配置Tomcat端口
server:
port: 8090
#配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
#配置redis
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: root
url: jdbc:mysql://localhost:3306/wechatsubscription?useUnicode=true&characterEncoding=UTF-8
#配置日志
logging:
level:
root: info
com.chenyx.mapper: debug
#配置Mybatis-Plus
mybatis-plus:
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
cache-enabled: true
map-underscore-to-camel-case: true
在启动类上添加@EnableCaching注解
package com.chenyx;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@MapperScan("com.chenyx.mapper")
@SpringBootApplication
@EnableCaching // 开启缓存
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
System.out.println("Spring-Boot启动成功!");
}
}
设置RedisTemplate,在这可以对Redis的存储类型进行序列化,但是这里我没有使用
package com.chenyx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @Author 陈雲鑫
* @Date 2021/7/24 17:23
*/
@Configuration
public class RedisConfiguration {
/**
* 设置redisTemplate
*/
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
创建MybatisRedisCache类重写Mybatis二级缓存的Cache接口的实现
package com.chenyx.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* 使用redis实现Mybatis Plus二级缓存
*
* @Author 陈雲鑫
* @Date 2021/7/24 10:42
*/
@Slf4j
public class MybatisRedisCache implements Cache {
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplate redisTemplate;
private RedisTemplate getRedisTemplate(){
//通过ApplicationContextHolder工具类获取RedisTemplate
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) ApplicationContextHolder.getBeanByName("redisTemplate");
}
return redisTemplate;
}
private final String id;
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
//使用redis的Hash类型进行存储
getRedisTemplate().opsForHash().put(id,key.toString(),value);
}
@Override
public Object getObject(Object key) {
try {
//根据key从redis中获取数据
return getRedisTemplate().opsForHash().get(id,key.toString());
} catch (Exception e) {
e.printStackTrace();
log.error("缓存出错 ");
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null) {
getRedisTemplate().delete(key.toString());
}
return null;
}
@Override
public void clear() {
log.debug("清空缓存");
Set<String> keys = getRedisTemplate().keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
getRedisTemplate().delete(keys);
}
}
@Override
public int getSize() {
Long size = (Long) getRedisTemplate().execute((RedisCallback<Long>) RedisServerCommands::dbSize);
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}
因为RedisTemplate的实例化需要使用Spring的工厂进行创建,而我们创建的MybatisRedisCache类实现的是Mybatis的Cache接口,所以这个类不是由工厂进行管理的,所以我们不能直接在该类中直接使用注解注入RedisTemplate,所以我们创建一个获取Spring Boot创建好的工厂的ApplicationContextHolder工具类,用于获取RedisTemplate
package com.chenyx.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
//根据bean name 获取实例
public static Object getBeanByName(String beanName) {
if (beanName == null || applicationContext == null) {
return null;
}
return applicationContext.getBean(beanName);
}
//只适合一个class只被定义一次的bean(也就是说,根据class不能匹配出多个该class的实例)
public static Object getBeanByType(Class clazz) {
if (clazz == null || applicationContext == null) {
return null;
}
return applicationContext.getBean(clazz);
}
public static String[] getBeanDefinitionNames() {
return applicationContext.getBeanDefinitionNames();
}
}
实现ApplicationContextAware接口后,在Spring Boot启动创建工厂后,就会自动调用这个接口的setApplicationContext方法,将创建的工厂以参数的形式传递给这个类,在这个方法中我们就可以把工厂给保存下来。
最后我们只需要在Mapper接口上添加@CacheNamespace注解,就完成了
package com.chenyx.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.chenyx.config.MybatisRedisCache;
import com.chenyx.entity.WeChatUser;
import org.apache.ibatis.annotations.CacheNamespace;
/**
* @Author 陈雲鑫
* @Date 2021/7/24 10:42
*/
@CacheNamespace(implementation= MybatisRedisCache.class,eviction=MybatisRedisCache.class)
public interface WeChatUserListMapper extends BaseMapper<WeChatUser>{
}
三、问题解决
applicationContext为Null
若创建的是多项目,ApplicationContextHolder工具类需要放在主项目中,不能放在别的工具类模块。