编程知识 cdmana.com

Mybatis的二级缓存、使用Redis做二级缓存

  什么是二级缓存?

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

  

image-20201111211828145

 

  如上图sqlSession1在查询时会从UserMapper的二级缓存中取,如果没有则执行数据库查询操作。

  然后写入到二级缓存中

  sqlSession2则执行同样的UserMapper查询时,会从UserMapper的二级缓存中取,此时的二级缓存中已经有内容了,所以就可以直接取到,不再与数据库交互。

  sqlSession3在执行事务操作(插入、更新、删除)时,会清空UserMapper的二级缓存

  1. 开启二级缓存

  如何使用二级缓存:

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

  在全局配置文件sqlMapConfig.xml中加入如下代码:

  

  

  

  

  其次在哪个namespace中开启二级就在哪里配置,因为mybatis有注解和xml两种方式所以:

  注解

  

image-20201111221707937

 

  注解扩展:

  //我们默认使用的是mybatis自带的二级缓存,它的实现在PerpetualCache类中,所以可以写成

  @CacheNamespace(implementation = PerpetualCache.class)

  //如果是使用redis作为二级缓存的话,下面第二部分会讲到

  xml

  

image-20201111221501041

 

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

  测试一:

  我们要根据用户id查询用户信息:

  

image-20201111222635859

 

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

  

image-20201111222950505

 

  最后执行看到打印日志:

  

image-20201111223421674

 

  为什么System.out.println(user1==user2)为false ?

  二级缓存和一级缓存不同,二级缓存缓存的不是对象,而是数据,在第二次查询时底层重新创建了一个User对象,并且把二级缓存中的数据重新封装成了对象并返回。所以user1和user2不是一个对象。

  测试二:

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

  

image-20201111224352601

 

  

image-20201111224330557

 

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

  userCache和flushCache

  还可以配置userCache和flushCacheuserCache : 是用来设置是否禁用二级缓存的,在statement中设置可以禁用当前select语句的二级缓存,即每次查询都会发出sql。默认情况为true.

  

image-20201111225137076

 

  

image-20201111225544937

 

  flushCache : 在mapper的同一个namespace中,如果有其它的增删改操作后需要刷新缓存,如果部执行刷新缓存会出现脏读。

  设置statement配置中的flushCache="true",即刷新缓存,如果改成false则不会刷新,有可能出现脏读。所以一般情况下没必要改

  

image-20201111225901338

 

  Mybatis二级缓存和一级缓存一样也是使用到了org.apache.ibatis.cache.impl.PerpetualCache这个类是mybatis的默认缓存类,同时,想要自定义缓存必须实现cache接口

  

image-20201112005725676

 

  2. 使用Redis实现二级缓存

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

  

image-20201112010039059

 

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

  

image-20201112010027075

 

  如何使用

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

  首先我们引入jar包

  

  org.mybatis.caches

  mybatis-redis

  1.0.0-beta2

  

  修改Mapper.xml文件

  //**********XML方式***********:

  

  

  //表示针对于当前的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接口

  

image-20201112011613642

 

  配置redis的配置文件

  redis.host=localhost

  redis.port=6379

  redis.connectionTimeout=5000

  redis.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());

  }

  });

  }

  }

  RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration();

  

image-20201112014625885

 

  RedisConfig中封装了默认的Redis配置信息 郑州不孕不育医院哪家好-36岁高龄1个月抱娃成功:https://yyk.fh21.com.cn/hospital_6369/tsyl/16697496.html

  

image-20201112014920980

 

  这个方法读取了我们配置在/resource/redis.properties这个文件

  RedisConfig后构建了Jedis池  郑州不孕不育医院介绍患有妊娠史女性影响怀孕吗:https://yyk.fh21.com.cn/hospital_6369/tsyl/16697499.html

  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和其他的序列化类差不多,负责对象的序列化和反序列化;

版权声明
本文为[程序猿1JIA]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4609373/blog/4714607

Tags jedis
Scroll to Top