mybatis 自定義redis作二級緩存
前言
若是關注功能實現,能夠直接看功能實現部分java
什麼時候使用二級緩存
一個宗旨---不常變的穩定而經常使用的node
- 一級是默認開啓的sqlsession級別的。
- 只在單表中使用,且全部的操做都是一個namespace下
- 查詢多 增刪改少的狀況下
- 緩存並不全是優勢,缺點很明顯,緩存有時不是最新的數據。
二級緩存參數說明
這是一個跨Sqlsession級雖的緩存,是mapper級別的,也就是能夠多個sqlsession訪問同一個mapper時生效redis
關鍵字 | 解讀 |
---|---|
eviction | 緩存回收策略 |
flushInterval | 刷新時間間隔,單位毫秒 |
size | 引用數目,表明緩存最多能夠存多少對象 |
readOnly | 是否只讀默認false 若是True全部的sql返回的是一個對象,性能高併發安全性底,若是false返回的是序列化後的副本,安全高效率底 |
經常使用的回收策略(與操做系統內存回收策略差很少)
- LRU 默認,最近最少使用
- FIFO 先進先出
- SOFT 軟引用 移除基於垃圾回收器狀態和軟引用規則的對象
- WEAK 弱引用 更積極的移除移除基於垃圾回收器狀態和弱引用規則的對象
sql中控制是否使用緩存及刷新緩存
<!-- 開啓二級緩存 --> <setting name="cacheEnabled" value="true"/> <select id="" resultMap="" useCache="false"> <update id="" parameterType="" flushCache="false" />
緩存什麼時候會失效(意思就是構成key的因素同樣,可是沒有命中value)
一級失效
- 不在同一個Sqlsession中,例如未開啓事務,mybatis每次查詢都會關閉舊的建立新的。
- 增刪改操做程序會clear緩存
- 手動執行sqlsession.clearCache()
二級失效
- insert ,update,delete語句的執行
- 超時
- 被回收策略回收
功能實現
添加依賴
<!-- 集成了lettuce的鏈接方式也能夠用jedis方式看本身建議用集成的說明穩定些 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 序列化 --> <dependency> <groupId>de.javakaffee</groupId> <artifactId>kryo-serializers</artifactId> </dependency> <!-- 鏈接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- 自定義獲取Bean --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <!-- 斷言判斷,正式環境中可使用 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
啓動二級緩存
- 能夠配置在調用mapper的項目中,方便之後維護
spring: redis: lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 #集羣的方式 # sentinel: # master: mymaster # nodes: 192.168.15.154:6379 database: 0 host: 192.168.15.154 port: 6379 # MyBatis Config properties mybatis: type-aliases-package: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl cache-enabled: true
自定義相關代碼
- 確保全部POJO都實現了序列化並聲明瞭序列號
private static final long serialVersionUID =-1L;
- 自定義實現緩存接口
/** * @author lyy * @description * @date 2019/9/2 */ public class RedisCache implements Cache { private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // cache instance id private final String id; private RedisTemplate redisTemplate; // redis過時時間 private static final long EXPIRE_TIME_IN_MINUTES = 30; public RedisCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; } @Override public String getId() { return id; } /** * Put query result to redis * * @param key * @param value */ @Override public void putObject(Object key, Object value) { try { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); logger.debug("Put query result to redis"); } catch (Throwable t) { logger.error("Redis put failed", t); } } /** * Get cached query result from redis * * @param key * @return */ @Override public Object getObject(Object key) { try { RedisTemplate redisTemplate = getRedisTemplate(); ValueOperations opsForValue = redisTemplate.opsForValue(); logger.debug("Get cached query result from redis"); return opsForValue.get(key); } catch (Throwable t) { logger.error("Redis get failed, fail over to db", t); return null; } } /** * Remove cached query result from redis * * @param key * @return */ @Override @SuppressWarnings("unchecked") public Object removeObject(Object key) { try { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.delete(key); logger.debug("Remove cached query result from redis"); } catch (Throwable t) { logger.error("Redis remove failed", t); } return null; } /** * Clears this cache instance */ @Override public void clear() { RedisTemplate redisTemplate = getRedisTemplate(); redisTemplate.execute((RedisCallback) connection -> { connection.flushDb(); return null; }); logger.debug("Clear all the cached query result from redis"); } /** * This method is not used * * @return */ @Override public int getSize() { return 0; } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } private RedisTemplate getRedisTemplate() { if (redisTemplate == null) { redisTemplate = ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; } }
- 定義ApplicationContextHolder來實現Bean的注入和獲取
/** * @author lyy * @description * @date 2019/9/2 */ @Component public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean { private static final Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class); private static ApplicationContext applicationContext; public static ApplicationContext getApplicationContext() { if (applicationContext == null) { throw new IllegalStateException( "'applicationContext' property is null,ApplicationContextHolder not yet init."); } return applicationContext; } /** * * 根據bean的id來查找對象 * * @param id * * @return * */ public static Object getBeanById(String id) { checkApplicationContext(); return applicationContext.getBean(id); } /** * * 經過名稱獲取bean * * @param name * * @return * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) { checkApplicationContext(); Object object = applicationContext.getBean(name); return (T) object; } /** * * 根據bean的class來查找對象 * * @param c * * @return * */ @SuppressWarnings("all") public static <T> T getBeanByClass(Class<T> c) { checkApplicationContext(); return (T) applicationContext.getBean(c); } /** * * 從靜態變量ApplicationContext中取得Bean, 自動轉型爲所賦值對象的類型. * 若是有多個Bean符合Class, 取出第一個. * * @param cluss * * @return * */ public static <T> T getBean(Class<T> cluss) { checkApplicationContext(); return (T) applicationContext.getBean(cluss); } /** * * 名稱和所需的類型獲取bean * * @param name * * @param cluss * * @return * */ public static <T> T getBean(String name, Class<T> cluss) { checkApplicationContext(); return (T) applicationContext.getBean(name, cluss); } public static <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException { checkApplicationContext(); return applicationContext.getBeansOfType(type); } /** * 檢查ApplicationContext不爲空. */ private static void checkApplicationContext() { if (applicationContext == null) { throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義ApplicationContextHolderGm"); } } @Override public void destroy() throws Exception { checkApplicationContext(); } /** * 清除applicationContext靜態變量 */ public static void cleanApplicationContext() { applicationContext = null; } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { //checkApplicationContext(); applicationContext = context; logger.info("holded applicationContext,顯示名稱:" + applicationContext.getDisplayName()); } }
啓用緩存
- 註解的方式,註解在mapper接口上
@CacheNamespace(implementation = RedisCache.class)
- xml的方式
<cache type="***.RedisCache"> <property name="eviction" value="LRU"/> <property name="flushInterval" value="60000000"/> <property name="size" value="2048"/> <property name="readOnly" value="false"/> </cache>
爲何使用redis?除了redis還有其它什麼?
- redis是一個高性能nosql庫,配上集羣穩定高效,由於默認的二級緩存是在內存中的。這樣能夠把緩存獨立出來,也是全部第三方map結構作二級緩存的優勢
- 能夠更好的適應分佈式
- 還有ehcache,還有理論上的全部nosql庫應該均可以
注意事項
緩存不生效(或者叫緩存穿透)
- 註解的sql使用註解開啓,xml配置的sql要在xml中開啓,混用不生效
- windows版的redis要配置一下容許外網鏈接,即便把redis.windows.conf中的bind 127.0.0.1註釋掉了也不行
# Examples: # # bind 192.168.1.100 10.0.0.1 bind 0.0.0.0
- intellij idea 生成序列號,下面選中後放光標放在類是,不是接口上按alt+enter
File -> Settings -> Inspections -> Serialization issues -> Serialization class without ‘serialVersionUID’