通大多数ORM层框架一样,Mybatis自然也提供了对一级缓存和二级缓存的支持。一下是一级缓存和二级缓存的作用于和定义。
1、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
2、一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写 到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存 也就不存在了。Mybatis默认开启一级缓存。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同 namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二 次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
一般的我们将Mybatis和Spring整合时,mybatis-spring包会自动分装sqlSession,而Spring通过动态代理 sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(), 执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无 法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的
因此我们一般在项目中实现Mybatis的二级缓存,虽然Mybatis自带二级缓存功能,但是如果实在集群环境下,使用自带的二级缓存只是针对单个的节 点,所以我们采用分布式的二级缓存功能。一般的缓存NoSql数据库如redis,Mancache等,或者EhCache都可以实现,从而更好地服务 tomcat集群中ORM的查询。
下面主要通过Redis实现Mybatis的二级缓存功能。
mybatis:
mapper-locations: classpath:mapping/*Mapper.xml
type-aliases-package: com.*.traffic
configuration:
map-underscore-to-camel-case: true
cache-enabled: true # 开启二级缓存
Mybatis提供了第三方Cache实现的接口,我们自定义MybatisRedisCache实现Cache接口,代码如下:
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Description Redis对Mybatis的缓存
* @Author Jack.Hu
*/
public class MybatisRedisCache implements Cache {
private static Logger log = LoggerFactory.getLogger(MybatisRedisCache.class);
// 读写锁
private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
// 这里使用redis缓存,使用springBoot自动注入
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private String id;
public MybatisRedisCache(final String id) {
if (null == id) {
throw new IllegalStateException("Cache instances require an ID");
}
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (redisTemplate == null) {
redisTemplate = (RedisTemplate<String, Object>) SpringContextHolder.getApplicationContext().getBean("redisTemplate");
}
if (value != null) {
redisTemplate.opsForValue().set(key.toString(), value);
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.lock;
}
@Override
public Object getObject(Object key) {
try {
if (null != key) {
return redisTemplate.opsForValue().get(key.toString());
}
} catch (Exception e) {
log.error("缓存错误");
}
return null;
}
@Override
public Object removeObject(Object key) {
if (null != key) {
return redisTemplate.delete(key.toString());
}
return null;
}
@Override
public void clear() {
log.debug("清空缓存");
if (null == redisTemplate) {
redisTemplate = (RedisTemplate<String, Object>) SpringContextHolder.getApplicationContext().getBean("redisJsonTemplate");
}
Set<String> keys = redisTemplate.keys("*:" + this.id + "*");
for (String key : keys) {
redisTemplate.delete(key);
}
}
/**
* 获取缓存中的缓存数量
*
* @return
*/
@Override
public int getSize() { // 获取缓存中的缓存数量
return redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize).intValue();
}
}
注意:由于RedisTemplate的注入可能在应用启动期间没有成功进行注入到缓存内中,需要在使用缓存时中进行手动注入,这是需要从应用的上下文获取RedisTemplate,需要定义一个获取应用上下文的类实现ApplicationContextAware接口,通过这个类来获取应用上下文,通过应用上下文来获取注册到应用上下文的bean!
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @ClassName SpringContextHolder
* @Description TODO
* @Author Jack Hu
*/
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
assertApplicationContext();
return applicationContext;
}
public static <T>T getBean(String beanName){
assertApplicationContext();
return (T)applicationContext.getBean(beanName);
}
public static <T>T getBean(Class<T> requiredType){
assertApplicationContext();
return applicationContext.getBean(requiredType);
}
private static void assertApplicationContext(){
if(null == SpringContextHolder.applicationContext){
throw new RuntimeException("applicationContext属性为null,请检查是否注入成功!");
}
}
}
<cache
type="org.andy.shop.cache.MybatisRedisCache"
eviction="LRU"
flushInterval="6000000"
size="1024"
readOnly="false"
/>
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读
true:只读:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取数据,直接就会将数据在缓存中的引用交给用户 。不安全,速度快
false:读写(默认):mybatis觉得获取的数据可能会被修改
mybatis会利用序列化&反序列化的技术克隆一份新的数据给你。安全,速度相对慢
size:缓存存放多少个元素
type:指定自定义缓存的全类名(实现Cache接口即可)
redis会自动的将Sql+条件+Hash等当做key值,而将查询结果作为value,只有请求中的所有参数都符合,那么就会使用redis中的二级缓存。
Redis实现Mybatis的二级缓存
mybatis cache标签的参数
SpringBoot配置Redis对Mybatis的二级缓存进行缓存