mybatis實現自訂二級緩衝,最簡單的做法,實現一個org.apache.ibatis.cache.Cache介面就可以了
然後就是在sql xml檔案裡使用,<cache eviction="LRU" type="com.mark.demo.shiro.mybatis.cache.MyBatisRedisCache" ></cache>
這種簡單實現有更新操作時會到期同一個設定檔裡的查詢快取,但是跨設定檔就不好處理了。
為了處理跨設定檔更新到期,需要自己實現RedisCachingExecutor implements Interceptor
下面貼代碼:
package com.mark.demo.shiro.mybatis.cache;import java.io.Serializable;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;import org.apache.ibatis.cache.Cache;import org.apache.ibatis.cache.CacheKey;import org.apache.ibatis.cache.CacheKey;import com.mark.demo.shiro.utils.JedisUtils;import com.mark.demo.shiro.utils.ObjectUtils;/**hxp(hxpwangyi@126.com)*2017年9月8日**/public class MyBatisRedisCache implements Cache,Serializable{public static final String mybatis_cache_prefix="mybatis_cache";private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();private String id;public MyBatisRedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("緩衝沒有初始化id"); } this.id = id;}@Overridepublic String getId() { return this.id;}@Overridepublic int getSize() { return JedisUtils.getMapLen(mybatis_cache_prefix);}@Overridepublic void putObject(Object key, Object value) {CacheKey cacheKey=(CacheKey)key;String [] keyAry=cacheKey.toString().split(":");String myKey=keyAry[2]; JedisUtils.setMapField(mybatis_cache_prefix, ObjectUtils.serialize(myKey), value);}@Overridepublic Object getObject(Object key) {CacheKey cacheKey=(CacheKey)key;String [] keyAry=cacheKey.toString().split(":");String myKey=keyAry[2]; return JedisUtils.getMapFiled(mybatis_cache_prefix, ObjectUtils.serialize(myKey)); }@Overridepublic Object removeObject(Object key) { Object ret=JedisUtils.getMapFiled(mybatis_cache_prefix, ObjectUtils.serialize(key));JedisUtils.removeMapField(mybatis_cache_prefix, ObjectUtils.serialize(key));return ret;}@Overridepublic void clear() { JedisUtils.del(mybatis_cache_prefix);}@Overridepublic ReadWriteLock getReadWriteLock() { return readWriteLock;}}
package com.mark.demo.shiro.mybatis.cache;import java.util.HashSet;import java.util.Properties;import java.util.Set;import org.apache.ibatis.cache.CacheKey;import org.apache.ibatis.executor.Executor;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.Invocation;import org.apache.ibatis.plugin.Plugin;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;/**hxp(hxpwangyi@126.com)*2017年9月8日**/@Intercepts(value = {@Signature(args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}, method = "query", type = Executor.class),@Signature(args = {MappedStatement.class, Object.class}, method = "update", type = Executor.class),@Signature(args = {boolean.class}, method = "commit", type = Executor.class),@Signature(args = {boolean.class}, method = "rollback", type = Executor.class),@Signature(args = {boolean.class}, method = "close", type = Executor.class)})public class RedisCachingExecutor implements Interceptor {private Set<String> updateStatementOnCommit = new HashSet<String>();RedisCachingManager cachingManager = RedisCachingManagerImpl.getInstance();public Object intercept(Invocation invocation) throws Throwable {String name = invocation.getMethod().getName();Object result =null;if("query".equals(name)){result = this.processQuery(invocation);}else if("update".equals(name)){result = this.processUpdate(invocation);}else if("commit".equals(name)){result = this.processCommit(invocation);}else if("rollback".equals(name)){result = this.processRollback(invocation);}else if("close".equals(name)){result = this.processClose(invocation);}return result;}public Object plugin(Object target) {return Plugin.wrap(target, this);}/** * when executing a query operation * 1. record this statement's id and it's corresponding Cache Object into Global Caching Manager; * 2. record this statement's id and * @param invocation * @return * @throws Throwable */protected Object processQuery(Invocation invocation) throws Throwable {Object result = invocation.proceed();return result;}protected Object processUpdate(Invocation invocation) throws Throwable {Object result = invocation.proceed();MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];updateStatementOnCommit.add(mappedStatement.getId());return result;}protected Object processCommit(Invocation invocation) throws Throwable {Object result = invocation.proceed();refreshCache();return result;}protected Object processRollback(Invocation invocation) throws Throwable { Object result = invocation.proceed(); clearSessionData();return result;}protected Object processClose(Invocation invocation) throws Throwable {Object result = invocation.proceed();boolean forceRollback = (Boolean) invocation.getArgs()[0];if(forceRollback){clearSessionData();}else{refreshCache();}return result;}/** * when the sqlSession has been committed,rollbacked,or closed, * session buffer query CacheKeys and update Statement collections should be cleared. * * 當SqlSession 執行了commit()、rollback()、close()方法, * Session層級的查詢語句產生的CacheKey集合以及 執行的更新語句集合應該被清空 */private synchronized void clearSessionData(){ updateStatementOnCommit.clear();}/** * refresh the session cache,there are two things have to do: * 1. add this session scope query logs to global cache Manager * 2. clear the related caches according to the update statements as configured in "dependency" file * 3. clear the session data */private synchronized void refreshCache(){cachingManager.clearRelatedCaches(updateStatementOnCommit);clearSessionData();}/** * * * Executor外掛程式配置資訊載入點 * properties中有 "dependency" 屬性來指示 配置的緩衝依賴配置資訊,讀取檔案,初始化EnhancedCacheManager */public void setProperties(Properties properties) {if(!cachingManager.isInitialized()){cachingManager.initialize(properties);}}}
package com.mark.demo.shiro.mybatis.cache;import java.util.Properties;import java.util.Set;import org.apache.ibatis.cache.Cache;public interface RedisCachingManager {public boolean isInitialized();/** * MyBatis是否開啟了二級緩衝,即 <setting name="cacheEnabled" value="true"/> * @return */public boolean isCacheEnabled();/** * * 初始化 緩衝管理器,應該只被調用一次; * * @param properties * properties 中至少包含兩個屬性: * dependency : 該值表示著緩衝依賴設定檔的位置 * cacheEnbled: "true" or "false",該配置必須要與<setting name="cacheEnabled">的值保持一致 */public void initialize(Properties properties);/** * 整個外掛程式的核心,根據指定的statementId更新與之關聯的二級緩衝 * 傳入的StatementId集合是由會話層級的update語句對應的StatementId, * EnhancedCachingManager將通過查詢相應的StatementId去查看是否配置了依賴關係,如果有,則將依賴關係中的statementId的查詢快取全部清空 * @param set */public void clearRelatedCaches(Set<String> set);}
package com.mark.demo.shiro.mybatis.cache;import java.io.IOException;import java.io.InputStream;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Properties;import java.util.Set;import java.util.concurrent.ConcurrentHashMap;import org.apache.ibatis.cache.Cache;import org.apache.ibatis.io.Resources;import org.apache.ibatis.parsing.XNode;import org.apache.ibatis.parsing.XPathParser;import com.mark.demo.shiro.utils.JedisUtils;import com.mark.demo.shiro.utils.PropertiesLoader;public class RedisCachingManagerImpl implements RedisCachingManager{//每一個statementId 更新依賴的statementId集合private Map<String,Set<String>> observers=new ConcurrentHashMap<String,Set<String>>();private boolean initialized = false;private boolean cacheEnabled = false;private volatile static RedisCachingManagerImpl enhancedCacheManager;private RedisCachingManagerImpl(){}public static RedisCachingManagerImpl getInstance(){if(enhancedCacheManager==null){synchronized (RedisCachingManagerImpl.class) {if(enhancedCacheManager==null){enhancedCacheManager=new RedisCachingManagerImpl();}}}return enhancedCacheManager;}public void clearRelatedCaches(final Set<String> set) {for(String observable:set){Set<String> relatedStatements = observers.get(observable);for(String statementId:relatedStatements){JedisUtils.removeMapField(MyBatisRedisCache.mybatis_cache_prefix, statementId);}}}public boolean isInitialized() {return initialized;}public void initialize(Properties properties){PropertiesLoader loader=new PropertiesLoader("mybatis.properties");String dependency = loader.getProperty("dependencys");if(!("".equals(dependency) || dependency==null)){InputStream inputStream;try {inputStream = Resources.getResourceAsStream(dependency);XPathParser parser = new XPathParser(inputStream);List<XNode> statements = parser.evalNodes("/dependencies/statements/statement");for(XNode node :statements){Set<String> temp = new HashSet<String>();List<XNode> obs = node.evalNodes("observer");for(XNode observer:obs){temp.add(observer.getStringAttribute("id"));}this.observers.put(node.getStringAttribute("id"), temp);}initialized = true;} catch (IOException e) {e.printStackTrace();}}//cacheEnabledString cacheEnabled = properties.getProperty("cacheEnabled", "true");if("true".equals(cacheEnabled)){this.cacheEnabled = true;}}public boolean isCacheEnabled() {return cacheEnabled;}}
<?xml version="1.0" encoding="UTF-8"?><dependencies> <statements> <!-- 角色授權後重新整理角色緩衝 --> <statement id="com.mark.demo.shiro.mapper.MenuMapper.updateMenu"> <observer id="com.mark.demo.shiro.mapper.UserMapper.getMenuTopLever"/> </statement> </statements></dependencies>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <!-- 全域參數 --> <settings> <!-- 使全域的映射器啟用或禁用緩衝。 --> <setting name="cacheEnabled" value="true"/> <!-- 全域啟用或禁用消極式載入。當禁用時,所有關聯對象都會即時載入。 --> <setting name="lazyLoadingEnabled" value="false"/> <!-- 當啟用時,有消極式載入屬性的對象在被調用時將會完全載入任意屬性。否則,每種屬性將會按需要載入。 --> <setting name="aggressiveLazyLoading" value="true"/> <!-- 是否允許單條sql 返回多個資料集 (取決於驅動的相容性) default:true --> <setting name="multipleResultSetsEnabled" value="true"/> <!-- 是否可以使用列的別名 (取決於驅動的相容性) default:true --> <setting name="useColumnLabel" value="true"/> <!-- 允許JDBC 產生主鍵。需要磁碟機支援。如果設為了true,這個設定將強制使用被產生的主鍵,有一些磁碟機不相容不過仍然可以執行。 default:false --> <setting name="useGeneratedKeys" value="false"/> <!-- 指定 MyBatis 如何自動對應 資料基表的列 NONE:不隱射 PARTIAL:部分 FULL:全部 --> <setting name="autoMappingBehavior" value="PARTIAL"/> <!-- 這是預設的執行類型 (SIMPLE: 簡單; REUSE: 執行器可能重複使用prepared statements語句;BATCH: 執行器可以重複執行語句和批次更新) --> <setting name="defaultExecutorType" value="REUSE"/> <!-- 使用駝峰命名法轉換欄位。 --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 設定本機快取範圍 session:就會有資料的共用 statement:語句範圍 (這樣就不會有資料的共用 ) defalut:session --> <setting name="localCacheScope" value="STATEMENT"/> <!-- 設定但JDBC類型為空白時,某些驅動程式 要指定值,default:OTHER,插入空值時不需要指定類型 --> <setting name="jdbcTypeForNull" value="NULL"/> </settings><typeAliases> <package name="com.mark.demo.shiro.entity" /></typeAliases> <!-- 外掛程式配置 --> <plugins> <plugin interceptor="com.mark.demo.shiro.mybatis.PaginationInterceptor"/> <plugin interceptor="com.mark.demo.shiro.mybatis.cache.RedisCachingExecutor"> <property name="dependency" value="dependencys.xml"/> <property name="cacheEnabled" value="true"/> </plugin> </plugins></configuration>
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mark.demo.shiro.mapper.UserMapper"><cache eviction="LRU" type="com.mark.demo.shiro.mybatis.cache.MyBatisRedisCache" ></cache> <resultMap type="User" id="userMap"> <result column="userId" property="id" /> <result column="userName" property="userName" /> <result column="password" property="password" /> <result column="age" property="age" /> <result column="sex" property="sex" /> <result column="phone" property="phone" /> </resultMap> <select id="getUserByUserName" parameterType="String" resultMap="userMap"> select * from user where userName=#{userName} </select> <resultMap type="Menu" id="menuMap"> <result column="menuId" property="id"/> <result column="menuName" property="menuName"/> <result column="menuDesc" property="menuDesc"/> <result column="link" property="link"/> <result column="icon" property="icon"/> </resultMap> <select id="getMenuTopLever" resultMap="menuMap" useCache="true"> select * from menu where pid=-1 </select></mapper>
demo地址:https://github.com/13567436138/shiro-demo.git