mybatis 一级缓存,二级缓存

今天我在使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值