美文网首页
mybatis缓存使用

mybatis缓存使用

作者: 我是许仙 | 来源:发表于2020-06-28 17:25 被阅读0次

    缓存的定义

    缓存一般是orm框架会提供的功能,目的是提升查询效率和减少数据库的压力。Mybatis有1级缓存和2级缓存,并且还有集成第三方的缓存工具比如说 redis。

    一级缓存

    一级缓存是默认开启的不需要配置。同时因为它是sqlSession级别的也叫会话缓存。在同一个会话里面,多次执行相同的 SQL 语句,会直接从内存取到缓存的结果,不会再发送 SQL 到数据库。但是不同的会话里面,即使执行的 SQL 一模一样(通过一个 Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

    命中缓存

    @Test
    public void selectListCacheOne() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        List<User> userList1 = userMapper1.selectList();
    
        UserMapper userMapper2 = sqlSession1.getMapper(UserMapper.class);
        List<User> userList2 = userMapper2.selectList();
        
    }
    

    返回结果是:

    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5ccddd20]
    ==>  Preparing: select * from user 
    ==> Parameters: 
    interface java.util.List
    

    上面的代码同一个sqlSession中执行2次查询 只打印出了一次sql语句。可见第2次查询命中了缓存。

    未命中缓存

    @Test
    public void selectListNoneCacheOne() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        List<User> userList1 = userMapper1.selectList();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        List<User> userList2 = userMapper2.selectList();
    
    }
    

    返回结果

    ==>  Preparing: select * from user 
    ==> Parameters: 
    interface java.util.List
    
    ==>  Preparing: select * from user 
    ==> Parameters: 
    interface java.util.List
    

    2个sqlsession执行了2次查询而sql语句打印了2次。由此可见不同不同的sqlsession查询同一个sql不会命中缓存。

    一级缓存的弊端

    我们知道一级缓存是sqlsession级别的。那么我在方法1中用sqslsession查询语句

    image-20200623110132109.png

    同时打上断点。在这个时候执行另一个线程插入一条数据

    @Test
    public void mybatisMapperInsert() {
        //4 获取sqlSession SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //5 获取sql 通过代理模式 代理了一个映射器
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User userParam = new User();
        userParam.setAge(1);
        userParam.setSexState(SexEnum.MAN);
        userParam.setSexStateOrigin(SexEnumOrigin.MAN);
        userMapper.insert(userParam);
        sqlSession.commit();
    }
    

    然后这个时候再次执行方法1中的线程。

    image-20200623110356167.png

    可以看到在同一个sqlsession中命中了缓存,第2次查询结果是还是13个。但是数据库中明明是14条数据。所以可见多线程的情况下 一级缓存会出现脏读的情况。 那么怎么避免呢?用2级缓存

    二级缓存

    二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别 的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享), 生命周期和应用同步。作为一个作用范围更广的缓存,它肯定是在 SqlSession 的外层,否则不可能被多个 SqlSession 共享。而一级缓存是在 SqlSession 内部的,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。

    image-20200623111635652.png

    开启二级缓存

    在想要开启二级缓存的Mapper中添加表桥<cache/>

    <mapper namespace="com.mybatis.demo.mybatis.mapper.UserMapper">
    
        <cache eviction="LRU" flushInterval="600000" size="1024" readOnly="true"/>
    
    

    属性 含义 取值
    type 2级缓存的实现类 默认是需要实现 Cache 接口,默认是 PerpetualCache
    size 最多缓存对象数量 默认是1024
    eviction 回收策略 LRU(默认) FIFO SOFT WEAK
    flushInterval 定时自动清理缓存 自动刷新时间,单位 ms,未配置时只有调用时刷新
    readOnly 是否只读 false 默认 crud操作会更新缓存 true:只读缓存 性能提高
    blocking 是否使用可重入锁实现 缓存的并发控制 true,会使用 BlockingCache 对 Cache 进行装饰 默认 false

    Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert() 会刷新缓存。

    如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办? 我们可以在单个 Statement ID 上显式关闭二级缓存(默认是 true):

    <select id="selectList"  resultMap="BaseResultMap" useCache="false">
         select * from user
    </select>
    

    这样子在命名空间com.mybatis.demo.mybatis.mapper.UserMapper就开启了一个缓存

    例子

    @Test
    public void selectListCacheTwoUpdate() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        List<User> userList1 = userMapper1.selectList();
        sqlSession1.close();
    
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        List<User> userList2= userMapper2.selectList();
        sqlSession2.close();
    }
    
    返回结果
    Cache Hit Ratio [com.mybatis.demo.mybatis.mapper.UserMapper]: 0.5
    

    打印出这个信息代表了命中缓存。

    集成redis缓存

    mybatis提供了集成redis的缓存 引入maven

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-redis</artifactId>
        <version>1.0.0-beta2</version>
    </dependency>
    

    mapper中添加缓存的type

    <cache type="org.mybatis.caches.redis.RedisCache"  flushInterval="60000" size="512" />
    

    执行的效果与上面的一样

    spring+redis+mybatis缓存

    mybatis虽然提供了redis缓存但是没有提供对spring的支持。所以想要在spring缓存下使用需要自己实现对myabtis缓存的支持需要集成接口 org.apache.ibatis.cache.Cache

    @Component
    public class MybatisRedisCache implements Cache {
    
      
        /**
         * ID
         */
        private String id;
    
        /**
         * 集成redisTemplate
         */
        private  static RedisTemplate redisTemplate;
    
    
        public MybatisRedisCache() {
        }
    
        public MybatisRedisCache(String id) {
            if (id == null) {
                throw new IllegalArgumentException("Cache instances require an ID");
            } else {
                this.id = id;
            }
        }
    
    
        @Override
        public String getId() {
            return this.id;
        }
    
        @Override
        public int getSize() {
            try {
                Long size = redisTemplate.opsForHash().size(this.id.toString());
                return size.intValue();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        @Override
        public void putObject(final Object key, final Object value) {
            try {
                redisTemplate.opsForHash().put(this.id.toString(), key.toString(), value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public Object getObject(final Object key) {
            try {
                Object hashVal = redisTemplate.opsForHash().get(this.id.toString(), key.toString());
                return hashVal;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        @Override
        public Object removeObject(final Object key) {
            try {
                redisTemplate.opsForHash().delete(this.id.toString(), key.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        public void clear() {
            try {
                redisTemplate.delete(this.id.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public String toString() {
            return "MybatisRedisCache {" + this.id + "}";
        }
    
    
        public static void setRedisTemplate(RedisTemplate redisTemplate) {
            MybatisRedisCache.redisTemplate = redisTemplate;
        }
    
    
    }
    

    private static RedisTemplate redisTemplate 为了集成spring redis,实现的缓存类中需要静态注入redisTemplate。通过静态方法

    public class RedisCacheTransfer {
    
    
        @Autowired
        public void serRedisTemplate(RedisTemplate redisTemplate) {
            MybatisRedisCache.setRedisTemplate(redisTemplate);
        }
    
    @Bean
    public RedisCacheTransfer redisCacheTransfer(RedisTemplate redisTemplate) {
        RedisCacheTransfer redisCacheTransfer = new RedisCacheTransfer();
        redisCacheTransfer.serRedisTemplate(redisTemplate);
        return redisCacheTransfer;
    }
    

    结果

    image-20200628165800830.png

    cache 有多个实例,采用的装饰器模式

    2级缓存 要等session关闭才会放入到缓存中 这是为什么

    相关文章

      网友评论

          本文标题:mybatis缓存使用

          本文链接:https://www.haomeiwen.com/subject/gkmwfktx.html