mybatis缓存使用

2020-06-28  本文已影响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关闭才会放入到缓存中 这是为什么

上一篇下一篇

猜你喜欢

热点阅读