写点什么

面试重灾区:请说说 mybatis 的一级缓存和二级缓存

用户头像
小黄鸡1992
关注
发布于: 1 小时前
面试重灾区:请说说mybatis的一级缓存和二级缓存

1.理解 mybatis 缓存

Mybatis 的一级缓存和二级缓存是 Mybatis 自带的。


目的:将 sql 的查询结果存放于缓存内,在接下来再次查询时,会直接从缓存中取出而不是从数据库里面获取。这样会提升查询速度,但是会产生脏读。


注意:一级缓存是自动开启的,二级缓存是需要手动开启的。所以开启二级缓存需要慎重考虑。(使用 spring boot 环境)

2.一级缓存

一级缓存是自动开启的。是 sqlSession 级别的缓存。


个人理解就是同一个事务之内,调取两次相同 sql。会读取一级缓存。


我们只要满足以下条件就可以使用一级缓存:


  • 1.sqlSession 级别的缓存。

  • 2.使用 @Transactional。(必须有此注解)


此处只贴上 service 的代码,其余的就是普通的 mvc 查询代码很简单。


       @Transactional  public City selectCity(int id) {       System.out.println("第一次查询");      redisDao.selectAnno(id);           //调用dao层查询方法      System.out.println("第二次查询");    return redisDao.selectAnno(id);  //调用dao层查询方  }
复制代码


这样我们就可以看到查询 sql。经过测试发现两次相同的查询只会打印出一条 sql。


spring boot 中使用开启查看 sql 的功能,需要指定 dao 层的包的位置。


logging.level.com.example.redis.dao=debug
复制代码

3.二级缓存

1.什么是二级缓存

二级缓存是 mapper 级别的,只要是对一个 mapper 调用就可以使用二级缓存。

2.查询顺序

在 Mybatis 中查询的顺序是:二级缓存----一级缓存-------数据库。

3.使用 redis 作二级缓存

首先连接 redis,实现 Cache 接口,实现接口中的方法,从 redis 取值和存值。这里使用了 RedisTemplate 。


public class MybatisRedisCache implements Cache {
private static Logger log = LoggerFactory.getLogger(MybatisRedisCache.class); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); private RedisTemplate redisTemplate; private String id; private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisRedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; }
@Override public void clear() { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.execute((RedisCallback) connection -> { connection.flushDb(); return null; }); log.debug("Clear all the cached query result from redis");
}
@Override public String getId() { // TODO Auto-generated method stub return this.id; }
@Override public Object getObject(Object key) { RedisTemplate redisTemplate = getRedisTemplate(); //ValueOperations opsForValue = redisTemplate.opsForValue(); log.debug("Get cached query result from redis"); //return opsForValue.get(key); Object obj = redisTemplate.opsForValue().get(key.toString()); return obj;
}
@Override public ReadWriteLock getReadWriteLock() { return readWriteLock; }
@Override public int getSize() { return 0; }
@Override @SuppressWarnings("uncheck") public void putObject(Object key, Object value) { //RedisTemplate redisTemplate = getRedisTemplate(); //ValueOperations opsForValue = redisTemplate.opsForValue(); //opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);
log.debug("Put query result to redis");
}
@Override public Object removeObject(Object key) { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.delete(key); log.debug("Remove cached query result from redis"); return null; }
private RedisTemplate getRedisTemplate() { if (redisTemplate == null) { redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; }
}
复制代码


但是 RedisTemplate 不是 spring 提供的,不能用 spring 的 ioc 进行依赖注入,所以需要我们手写来注册 RedisTemplate 。


@Componentpublic class ApplicationContextHolder implements ApplicationContextAware {  private static ApplicationContext applicationContext;
@Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { applicationContext = ctx; }
/** * Get application context from everywhere * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; }
/** * Get bean by class * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); }
/** * Get bean by class name * * @param name * @param <T> * @return */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) { return (T) applicationContext.getBean(name); } }
复制代码


然后在 mapper 文件中开启二级缓存。


<mapper namespace="com.example.redis.dao.RedisDao"><!-- 开启二级缓存 --><cache type="com.example.redis.cache.MybatisRedisCache"></cache>   <select id="selectAnno" resultType="com.example.redis.entity.City">    select * from city     <if test="id != null">where id = #{id}</if>  </select>  <insert id="insertIn">    INSERT INTO `mysql`.`city` (`id`,    `provinceId`, `cityName`, `description`) VALUES ('1', '1', 'aa', 'aa')  </insert></mapper>
复制代码


最后需要序列化,要不会在 redis 存放乱码。


@Configurationpublic class RedisConfig {
@Autowired private RedisTemplate redisTemplate; @Bean public RedisTemplate redisTemplateInit() { Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); // 设置序列化Key的实例化对象 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 设置序列化Value的实例化对象 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; }}
复制代码


然后我们测试,在不同的 sqlsession 中执行同一条 sql 时,只有第一次会打印出 sql。我们在 redis 中会发现,执行一次 sql 后会在 redis 存储本次的查询结果。

4.结论

一级缓存:同一个事务之内,调取两次相同 sql。会读取一级缓存。


二级缓存:mapper 级别的,只要是对一个 mapper 调用就可以使用二级缓存。

用户头像

小黄鸡1992

关注

还未添加个人签名 2021.07.13 加入

还未添加个人简介

评论

发布
暂无评论
面试重灾区:请说说mybatis的一级缓存和二级缓存