mybatis redis_带你从源码学习mybatis-Redis二级缓存,还不收藏

什么是二级缓存?

二级缓存和一级缓存的原理是一样的,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于的sqlSession,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如何两个mapper的namespace相同,即使两个mapper,那这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中

453d5643d01e485a70490c7201ea183b.png
  • 如上图sqlSession1在查询时会从UserMapper的二级缓存中取,如果没有则执行数据库查询操作。
  • 然后写入到二级缓存中
  • sqlSession2则执行同样的UserMapper查询时,会从UserMapper的二级缓存中取,此时的二级缓存中已经有内容了,所以就可以直接取到,不再与数据库交互。
  • sqlSession3在执行事务操作(插入、更新、删除)时,会清空UserMapper的二级缓存

1. 开启二级缓存

如何使用二级缓存:

mybatis中,一级缓存是默认开启的,但是二级缓存需要配置才可以使用

在全局配置文件sqlMapConfig.xml中加入如下代码:
  1. 其次在哪个namespace中开启二级就在哪里配置,因为mybatis有注解和xml两种方式所以:

注解

001b58d790aa3909e1dfe0920cfb0529.png

注解扩展://我们默认使用的是mybatis自带的二级缓存,它的实现在PerpetualCache类中,所以可以写成@CacheNamespace(implementation = PerpetualCache.class)//如果是使用redis作为二级缓存的话,下面第二部分会讲到

xml

e5206f45ed0a40279cac888fe8258520.png

这样就开启了UserMapper的二级缓存

  1. 测试一:我们要根据用户id查询用户信息:
a2f22b4452eedf36f5ed4dd110f3003e.png

注意:将缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存的存储介质多种多样,不一定只在内存中,也可能在硬盘中,如果我们要再取出这个缓存的话,就需要反序列化了。所以mybatis的pojo都去实现Serializable接口

d115f843f27f0b500037eff969c28f3d.png

最后执行看到打印日志:

c215eb4a1fce4cefed1848ffb87463c0.png

为什么System.out.println(user1==user2)为false ?二级缓存和一级缓存不同,二级缓存缓存的不是对象,而是数据,在第二次查询时底层重新创建了一个User对象,并且把二级缓存中的数据重新封装成了对象并返回。所以user1和user2不是一个对象。

  1. 测试二: 我们在测试二中进行一下事务操作,看看是否能清空二级缓存:
ad17370e810513168805a3e2433af21b.png
b41f582aeaef1b0df2f5307e4313c890.png

增加了一个修改操作,发现执行了两个select,说明提交事务会刷新二级缓存

userCache和flushCache

还可以配置userCache和flushCache

  • userCache : 是用来设置是否禁用二级缓存的,在statement中设置可以禁用当前select语句的二级缓存,即每次查询都会发出sql。默认情况为true.
10a0081c50f10aa07b1a6e8e4159c4b3.png
6abe5e13a78266da68bd2bd2e18cfa29.png
  • flushCache : 在mapper的同一个namespace中,如果有其它的增删改操作后需要刷新缓存,如果不执行刷新缓存会出现脏读。 设置statement配置中的flushCache="true",即刷新缓存,如果改成false则不会刷新,有可能出现脏读。所以一般情况下没必要改
2cf02c3b1c0622a6656d220227b3f94f.png

Mybatis二级缓存和一级缓存一样也是使用到了org.apache.ibatis.cache.impl.PerpetualCache

这个类是mybatis的默认缓存类,同时,想要自定义缓存必须实现cache接口

3d154179af84bcad00723572d5ba76ae.png

2. 使用Redis实现二级缓存

Mybatis自带的二级缓存是有缺点的,就是这个缓存是单服务器进行工作的,无法实现分布式缓存。

f1dd60f11fe730f1ecb1e56c529e1c46.png

所以为了解决这个问题,必须找一个分布式缓存专门存放缓存数据。

4f10cab41ee8c4b1f11393c3ec69c1b0.png

如何使用

mybatis提供了一个针对cache接口的redis实现类,在mybatis-redis包中

首先我们引入jar包     org.mybatis.caches     mybatis-redis     1.0.0-beta2
修改Mapper.xml文件//**********XML方式***********:<?xml version="1.0" encoding="UTF-8"?>       //表示针对于当前的namespace开启二级缓存                 select * from user     
//*******注解方式**********@CacheNamespace(implementation = RedisCache .class)public interface UserMapper {    //根据id查询用户 注解使用    @Select("select * from user where id=#{id}")    public User findById(Integer id);

这个类同样实现了Cache接口

c5b1b7b12e529f7b73689d3ac89c30ab.png
  1. 配置redis的配置文件
redis.host=localhostredis.port=6379redis.connectionTimeout=5000redis.password=redis.database=0

测试方法同自带的二级缓存一样。

3. Redis二级缓存源码分析

RedisCache和Mybatis二级缓存的方案都差不多,无非是实现Cache接口,并使用jedis操作缓存,不过在设计细节上有点区别。 我们带着问题分析源码:

  • 在RedisCache类中如何向redis中进行缓存值的存取 ?
  • 使用了哪种数据结构 ?
package org.mybatis.caches.redis;import java.util.Map;import java.util.concurrent.locks.ReadWriteLock;import org.apache.ibatis.cache.Cache;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;//首先其实现了Cache接口,被mybatis初始化的时候的CacheBuilder创建//创建方式就是调用了下面的有参构造public final class RedisCache implements Cache {  private final ReadWriteLock readWriteLock = new DummyReadWriteLock();  private String id;  private static JedisPool pool;  //有参构造    public RedisCache(final String id) {    if (id == null) {      throw new IllegalArgumentException("Cache instances require an ID");    }    this.id = id;    //RedisConfigurationBuilder调用parseConfiguration()方法创建RedisConfig对象    RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();    //构建Jedis池    pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(),            redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(),            redisConfig.getDatabase(), redisConfig.getClientName());  }    //模板方法,下面的putObject和getObject、removeObject都会用到这个方法  private Object execute(RedisCallback callback) {    Jedis jedis = pool.getResource();    try {      return callback.doWithRedis(jedis);    } finally {      jedis.close();    }  }      //。。。。。。。。省略部分代码            @Override  public void putObject(final Object key, final Object value) {    execute(new RedisCallback() {      @Override      public Object doWithRedis(Jedis jedis) {        jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));        return null;      }    });  }​  @Override  public Object getObject(final Object key) {    return execute(new RedisCallback() {      @Override      public Object doWithRedis(Jedis jedis) {        return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));      }    });  }​  @Override  public Object removeObject(final Object key) {    return execute(new RedisCallback() {      @Override      public Object doWithRedis(Jedis jedis) {        return jedis.hdel(id.toString(), key.toString());      }    });  }​​}
  1. RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();
fa2913fbb2338c1de11411c8c8fd0f5a.png

RedisConfig中封装了默认的Redis配置信息

46332822321484a5b65d3d184975f1db.png

这个方法读取了我们配置在/resource/redis.properties这个文件 RedisConfig后构建了Jedis池

  1. put方法
private Object execute(RedisCallback callback) {    Jedis jedis = pool.getResource();    try {      return callback.doWithRedis(jedis);   } finally {      jedis.close();   } }​public void putObject(final Object key, final Object value) {    execute(new RedisCallback() {      @Override      public Object doWithRedis(Jedis jedis) {        jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));        return null;     }   }); }

我们可以看到,put方法调用了模板方法得到 一个jedis链接,然后调用doWithRedis()方法

jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));

可以很清楚地看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash的field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值