今天我在使用mybatis + pageHelper,使用过pageHelper的都知道,他会在每次查询时都进行count 计算数据行数,在使用limit 进行分页。可时当数据量较大时,count会占用较大时长,且每次查询都会需要查询count 。
PageHelper.startPage(req.getPageNum(),req.getPageSize());
查看pageHelper源码,发现又boolean 可以控制count是否进行
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
return startPage(pageNum, pageSize, count, (Boolean)null, (Boolean)null);
}
我们可以通过判断设置count 开关
Boolean countFlag = true;
if (req.getPageNum()>1){
countFlag = false;
}
PageHelper.startPage(req.getPageNum(),req.getPageSize(),countFlag);
以上是基本这个问题的一个解决方案,当然我感觉到很麻烦,首先每个接口都需要修改代码,且前端展示总页数时需要附加代码配合。有没有更加简便的方法。
cache 缓存
mybatis 的缓存机制就完美解决这个问题。我们来理解下。
(增、删、改是没有任何关联的,只有在查询时,才会遇到缓存,增删改不涉及)
MyBatis中的缓存分为两种:一级缓存和二级缓存。
一级缓存是sqlSession级别的,二级缓存是mapper级别的
一级缓存:
mybatis 默认开启一级缓存,我们要做是设置范围
#设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session
mybatis.configuration.local-cache-scope=session
如果不设置默认为session,查看源码就能看到
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
我们来体验下缓存:
List<LinkedHashMap<String, Object>> result = gameCostMapper.getGameCost(req);
List<LinkedHashMap<String, Object>> result2 = gameCostMapper.getGameCost(req);
List<LinkedHashMap<String, Object>> result3 = gameCostMapper.getGameCost(req);
List<LinkedHashMap<String, Object>> result4 = gameCostMapper.getGameCost(req);
List<LinkedHashMap<String, Object>> result5 = gameCostMapper.getGameCost(req);
List<LinkedHashMap<String, Object>> result6 = gameCostMapper.getGameCost(req);
如果缓存不起效果,应该查询5次,如果有效果则只查询1次
结果显示,查了5次,哪里有问题?
一级缓存的范围是一次sqlSession中使用缓存,而我们的查询创建了5次sqlSession。
ok,我们将这几个查询绑定为一个sqlSession ,即加上事务
@Transactional(rollbackFor = Throwable.class)
再来查询一下,只查询了一个。成功!
二级缓存:
当我们多个sql Session 想要使用缓存时,一级缓存满足不了。我们将范围扩展至Namespace。
首先开启二级缓存
mybatis.configuration.cache-enabled=true
配置需要缓存的类
@Mapper
@CacheNamespace
public interface GameCostMapper {
}
开始,连续请求三次,当缓存不生效时,三次返回时间相似,如果生效则时间大大减少
第一次请求时间为4s,第二次为0.4s,第三次为0.3s,成功!
我们设置下缓存刷新时间
@CacheNamespace(flushInterval = 30000)
第三方缓存
前面我们已经设置了二级缓存的了,但是mybatis 毕竟不是专业做缓存的,没有分布式共享,且没有办法手动清除缓存。我们可以使用redis进行第三方缓存,这些问题就可以解决了。
从CacheNamespace注解中有参数为implementation
默认为PerpetualCache.class
我们来写一个MybatisRedisCache
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Mybatis 二级缓存
*
* @author huiliuliu
* @date 2019-10-14
*/
@Slf4j
public class MybatisRedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id;
private RedisTemplate redisTemplate;
//redis过期时间
private static final long EXPIRE_TIME_IN_MINUTES = 30;
/**
* 创建对象
*
* @param id
*/
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instance required an ID");
}
this.id = id;
}
@Override
public String getId() {
return id;
}
/**
* 存
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
opsForValue.set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
log.info("结果成功放入缓存 and " + "key = {},value = {} ", key, value);
}
/**
* 取
* @param key
*/
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
Object value = opsForValue.get(key.toString());
log.info("结果从缓存中获取 ,key = {},value = {} ", key, value);
return value;
}
/**
* 清除缓存
* @param key
*/
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key);
log.info("从缓存中删除,key: {}",key);
return null;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
connection.flushDb();
return null;
});
log.info("清空缓存");
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute((RedisCallback) connection -> connection.dbSize());
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if(redisTemplate == null){
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
由于无法获取bean,所有我们需要一个spring 工具
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 管理
*
* @author huiliuliu
* @date 2019-10-14
*/
@Component
public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
checkApplicationContext();
return applicationContext;
}
public static <T> T getBean(String name){
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz){
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
public static void cleanApplicationContext(){
applicationContext = null;
}
private static void checkApplicationContext(){
if(applicationContext ==null){
if (applicationContext == null){
throw new IllegalStateException("applicationContext未注入,请在applicationContext.xml中定义SpringContext");
}
}
}
@Override
public void destroy() throws Exception {
}
}
此时替换下Mapper 中CacheNamespace
@CacheNamespace(implementation = MybatisRedisCache.class)
测试一下吧,友情提示,原先的flushInterval 参数无效了,原因是我们这里的失效事件是在MybatisRedisCache中写的
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
opsForValue.set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
log.info("结果成功放入缓存 and " + "key = {},value = {} ", key, value);
}
当然你也可以把这个失效时间改为flushInterval