一 mybatis二级缓存

1.1 简介

Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存,如图:

image.png

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。


1.2 设置步骤

1.2.1 设置开关

在工程application.properties设置开关

  1. mybatis.configuration.cache-enabled=true

1.2.2 实现cache接口

  1. package com.module.demo.dal.cache;
  2. import org.apache.ibatis.cache.Cache;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import org.springframework.data.redis.core.RedisCallback;
  6. import org.springframework.data.redis.core.RedisTemplate;
  7. import org.springframework.data.redis.core.ValueOperations;
  8. import java.util.concurrent.TimeUnit;
  9. import java.util.concurrent.locks.ReadWriteLock;
  10. import java.util.concurrent.locks.ReentrantReadWriteLock;
  11. /**
  12. * @className: RedisCache
  13. * @description: RedisCache 描述
  14. * @author: kangzeng.ckz
  15. * @date: 2021/12/14
  16. **/
  17. public class RedisCache implements Cache {
  18. private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
  19. private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  20. // cache instance id
  21. private final String id;
  22. private RedisTemplate redisTemplate;
  23. // redis过期时间,10分钟
  24. private static final long EXPIRE_TIME_IN_SECONDS = 600;
  25. public RedisCache(String id) {
  26. if (id == null) {
  27. throw new IllegalArgumentException("Cache instances require an ID");
  28. }
  29. System.out.println("id:"+id);
  30. this.id = id;
  31. }
  32. /**
  33. * @description: 方法定义
  34. * @param:
  35. * @return: java.lang.String
  36. * @author: kangzeng.ckz
  37. * @date: 2021/12/13
  38. */
  39. @Override
  40. public String getId() {
  41. System.out.println("id:"+id);
  42. return id;
  43. }
  44. /**
  45. * @description: 方法定义
  46. * @param: key
  47. * @param: value
  48. * @return: void
  49. * @author: kangzeng.ckz
  50. * @date: 2021/12/13
  51. */
  52. @Override
  53. @SuppressWarnings("unchecked")
  54. public void putObject(Object key, Object value) {
  55. try {
  56. RedisTemplate redisTemplate = getRedisTemplate();
  57. ValueOperations opsForValue = redisTemplate.opsForValue();
  58. opsForValue.set(key, value, EXPIRE_TIME_IN_SECONDS, TimeUnit.SECONDS);
  59. logger.info("Put query result to redis");
  60. }
  61. catch (Throwable t) {
  62. logger.error("Redis put failed", t);
  63. }
  64. }
  65. /**
  66. * @description: 方法定义
  67. * @param: key
  68. * @return: java.lang.Object
  69. * @author: kangzeng.ckz
  70. * @date: 2021/12/13
  71. */
  72. @Override
  73. public Object getObject(Object key) {
  74. try {
  75. RedisTemplate redisTemplate = getRedisTemplate();
  76. ValueOperations opsForValue = redisTemplate.opsForValue();
  77. logger.info("Get cached query result from redis");
  78. return opsForValue.get(key);
  79. }
  80. catch (Throwable t) {
  81. logger.error("Redis get failed, fail over to db", t);
  82. return null;
  83. }
  84. }
  85. /**
  86. * @description: 方法定义
  87. * @param: key
  88. * @return: java.lang.Object
  89. * @author: kangzeng.ckz
  90. * @date: 2021/12/13
  91. */
  92. @Override
  93. @SuppressWarnings("unchecked")
  94. public Object removeObject(Object key) {
  95. try {
  96. RedisTemplate redisTemplate = getRedisTemplate();
  97. redisTemplate.delete(key);
  98. logger.debug("Remove cached query result from redis");
  99. }
  100. catch (Throwable t) {
  101. logger.error("Redis remove failed", t);
  102. }
  103. return null;
  104. }
  105. /**
  106. * @description: 方法定义
  107. * @param:
  108. * @return: void
  109. * @author: kangzeng.ckz
  110. * @date: 2021/12/13
  111. */
  112. @Override
  113. public void clear() {
  114. RedisTemplate redisTemplate = getRedisTemplate();
  115. redisTemplate.execute((RedisCallback) connection -> {
  116. connection.flushDb();
  117. return null;
  118. });
  119. logger.debug("Clear all the cached query result from redis");
  120. }
  121. /**
  122. * This method is not used
  123. *
  124. * @return
  125. */
  126. @Override
  127. public int getSize() {
  128. return 0;
  129. }
  130. /**
  131. * @description: 方法定义
  132. * @param:
  133. * @return: java.util.concurrent.locks.ReadWriteLock
  134. * @author: kangzeng.ckz
  135. * @date: 2021/12/13
  136. */
  137. @Override
  138. public ReadWriteLock getReadWriteLock() {
  139. return readWriteLock;
  140. }
  141. private RedisTemplate getRedisTemplate() {
  142. if (redisTemplate == null) {
  143. redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
  144. }
  145. return redisTemplate;
  146. }
  147. }
  1. package com.module.demo.dal.cache;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. /**
  7. * @className: ApplicationContextHolder
  8. * @description: ApplicationContextHolder 描述
  9. * @author: kangzeng.ckz
  10. * @date: 2021/12/14
  11. **/
  12. @Component
  13. public class ApplicationContextHolder implements ApplicationContextAware {
  14. private static ApplicationContext applicationContext;
  15. @Override
  16. public void setApplicationContext(ApplicationContext ctx) throws BeansException {
  17. applicationContext = ctx;
  18. }
  19. /**
  20. * Get application context from everywhere
  21. *
  22. * @return
  23. */
  24. public static ApplicationContext getApplicationContext() {
  25. return applicationContext;
  26. }
  27. /**
  28. * Get bean by class
  29. *
  30. * @param clazz
  31. * @param <T>
  32. * @return
  33. */
  34. public static <T> T getBean(Class<T> clazz) {
  35. return applicationContext.getBean(clazz);
  36. }
  37. /**
  38. * Get bean by class name
  39. *
  40. * @param name
  41. * @param <T>
  42. * @return
  43. */
  44. @SuppressWarnings("unchecked")
  45. public static <T> T getBean(String name) {
  46. return (T) applicationContext.getBean(name);
  47. }
  48. }

1.2.3 在mapper文件设置cache

1.2.3.1 cache标签

在MyBatis的 Mapper XML 中加入 cache 或者 cache-ref 标签。

  • cache标签用于声明这个namespace需要使用二级缓存,并且可以自定义配置。
  • type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
  1. <!-- 添加二级缓存-->
  2. <cache type="com.module.demo.dal.cache.RedisCache"/>

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache

<cache-ref namespace="mapper.StudentMapper"/>

1.2.3.2 设置flushCache

flushCache表示是否更新缓存,在insert,update,delete添加flushCache=”true”,每次数据变更则会自动更新cache(不一定要设置,根据业务需要灵活变动)。

<delete id="deleteByExample" flushCache="true" parameterType="com.module.demo.dal.model.EmployeeExample">
        delete from employee employee
        <if test="_parameter != null">
            <include refid="Example_Where_Clause"/>
        </if>
    </delete>


1.3 Note

  • 最好只在单表操作的表上(确切是一个mapper文件)使用缓存,当然你得看你具体的业务需求,灵活设置cache空间和flushCache时机。
  • 不只是要保证这个表在整个系统中只有单表操作,而且和该表有关的全部操作必须全部在一个namespace下。缓存是以namespace为单位的,最好一个mapper使用一个namespace,不同namespace下的操作互不影响。
  • 一般情况下(具体业务场景具体分析),保证查询远远大于insert,update,delete操作的情况下使用缓存。


使用

二 Spring缓存

  • Cache:缓存抽象的规范接口,缓存实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
  • CacheManager:缓存管理器,管理Cache的生命周期

    2.1 JSR107核心接口

  • Java Caching(JSR-107)定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry和 Expiry。

  • CachingProvider:创建、配置、获取、管理和控制多个CacheManager
  • CacheManager:创建、配置、获取、管理和控制多个唯一命名的Cache,Cache存在于CacheManager的上下文中。一个CacheManager仅对应一个CachingProvider
  • Cache:是由CacheManager管理的,CacheManager管理Cache的生命周期,Cache存在于CacheManager的上下文中,是一个类似map的数据结构,并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有
  • Entry:是一个存储在Cache中的key-value对
  • Expiry:每一个存储在Cache中的条目都有一个定义的有效期。一旦超过这个时间,条目就自动过期,过期后,条目将不可以访问、更新和删除操作。缓存有效期可以通过ExpiryPolicy设置

引用尚硅谷视频课件中的图示:
image.png

2.2 缓存注解

Spring提供的重要缓存注解

  • @Cacheable:针对方法配置,能够根据方法的请求参数对其结果进行缓存
  • @CacheEvict:清空缓存
  • @CachePut:既调用方法,又更新缓存数据
  • @EnableCaching:开启基于注解的缓存
  • @Caching:定义复杂的缓存规则
  • @CacheConfig 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用

举例@Cacheable使用,首先设置keyGenerator,keyGenerator是生成redis缓存的key。

@Configuration
public class CacheConfig {
    @Bean(value = {"customKeyGenerator"})
    public KeyGenerator keyGenerator(){
        KeyGenerator keyGenerator = new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName() + "[" + Arrays.asList(params).toString() + "]";
            }
        };
        return keyGenerator;
    }
}

缓存使用,直接在方法(类)上添加注解,注解字段和值参考:https://www.cnblogs.com/coding-one/p/12401630.html

    @Override
    @Cacheable(value = {"emp"}, keyGenerator = "customKeyGenerator")
    public ListEmployeeResponse listEmployee(ListEmployeeRequest request) throws BaseException {
        ParamChecker.checkAllNotNull("request",request,request.getName());
        ListEmployeeResponse listEmployeeResponse = employeeManager.listEmployeeByName(request.getName(), request.getPageNumberWithDefault(), request.getPageSizeWithDefault());
        return listEmployeeResponse;
    }

redis运行后redis上会生成缓存,如

5) "emp::listEmployee[[ListEmployeeRequest(name=kang)]]"

三 redistemplate

https://juejin.cn/post/6881541490649071624

参考

https://www.cnblogs.com/mzq123/p/12629142.html

https://blog.csdn.net/qq_21508727/article/details/81908258