├── .classpath ├── .gitattributes ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── README.md ├── example.log ├── pom.xml └── src ├── main └── java │ └── org │ └── luanlouis │ └── mybatis │ └── plugin │ └── cache │ ├── CacheKeysPool.java │ ├── EnhancedCachingExecutor.java │ ├── EnhancedCachingManager.java │ ├── dependency.dtd │ └── impl │ └── EnhancedCachingManagerImpl.java └── test └── java ├── com └── louis │ └── mybatis │ ├── domain │ ├── DepartmentsMapper.xml │ └── EmployeesMapper.xml │ ├── model │ ├── Department.java │ └── Employee.java │ └── test │ └── SelectDemo3.java ├── dependencys.xml ├── log4j.properties └── mybatisConfig.xml /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | mybatis-enhanced-cache 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding/=UTF-8 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.5 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 12 | org.eclipse.jdt.core.compiler.source=1.5 13 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mybatis-enhanced-cache 2 | ====================== 3 | 4 | 简 介 5 | ---- 6 | MyBatis Enhanced Cache, Control your Caches precisely!
7 | 该插件主要是为了弥补MyBatis二级缓存控制上的不足,提高二级缓存Cache和数据库数据的同步性和一致性,处理各个Cache之间的关联关系。 8 | 该插件可以精确地管理MyBatis的二级缓存,实现对MyBatis二级缓存细粒度的控制。
9 | 当执行过对数据库表的更新操作(update、delete、insert)时,可以指定清除由特定的StatementId表示的查询语句产生的缓存。
10 | 11 | 应 用 场 景: 12 | ----- 13 | 当前的MyBaits对于缓存的比较粗糙,一般为一个Mapper配置一个Cache缓存,或者多个Mapper共用一个缓存。 14 | 而对缓存的维护都是独立的,缓存之间不会相互影响,指定的Mapper中的语句只会影响到该Mapper对应的Cache缓存。 15 | 有时候希望当执行某些更新操作时,能够刷新或者清空特定的查询语句产生的缓存,以避免数据不一致的情况。 16 | ###举例 17 | *** 18 | 现有AMapper.xml 中定义了对数据库表`ATable`的CRUD操作,BMapper定义了对数据库表`BTable`的CRUD操作;
19 | 假设MyBatis的二级缓存开启,并且AMapper中使用了二级缓存,AMapper对应的二级缓存为`ACache`;
20 | 除此之外,AMapper中还定义了一个跟BTable有关的查询语句,类似如下所述: 21 | ```xml 22 | 25 | ``` 26 | 27 | 1. 执行了`selectATableWithJoin`操作,该查询的缓存会被放置到对应的二级缓存`ACache`中, 28 | 当再次执行相同的查询,则直接从二级缓存`ACache`中; 29 | 2. 如果某个时候,BMapper中执行了对BTable的update操作(update 、delete、insert),BTable 的数据已经更新, 30 | 3. 再次执行`selectATableWithJoin`操作,该查询的结果直接从二级缓存`ACache`中取,这时候就造成了`**数据不一致**`的情况 31 | 32 | **针对上述的问题,需要解决**:
33 | **当BMapper执行对BTable的update操作时,指定刷新 `ACache`中的 `selectATableWithJoin`语句产生的缓存** 34 | 35 | 该插件正是解决上述的这一问题! 36 | 37 | 38 | 使用方法: 39 | ---- 40 | 使用此插件非常简单: 41 | 1. 在mybatisConfig.xml 文件中定义plugin节点如下: 42 | ```xml 43 | 44 | 45 | 46 | 47 | 48 | 49 | ``` 50 | 2. dependencys.xml配置文件,配置StatemntId依赖关系 51 | ```xml 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ``` 61 | * \节点配置更新语句和查询语句的依赖关系,如果id表示的更新语句执行了,会清空由配置的id表示的查询语句生成的缓存。 62 | * \节点表示当父节点 id表示的更新语句执行后,应该清除此语句所产生的缓存 63 | -------------------------------------------------------------------------------- /example.log: -------------------------------------------------------------------------------- 1 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------Cache Status------------ 2 | INFO main com.louis.mybatis.test.SelectDemo3 - EmployeesMapper:1 3 | INFO main com.louis.mybatis.test.SelectDemo3 - DepartmentsMapper:0 4 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.DepartmentsMapper:0 5 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.EmployeesMapper:1 6 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------------------------------ 7 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------Cache Status------------ 8 | INFO main com.louis.mybatis.test.SelectDemo3 - EmployeesMapper:2 9 | INFO main com.louis.mybatis.test.SelectDemo3 - DepartmentsMapper:0 10 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.DepartmentsMapper:0 11 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.EmployeesMapper:2 12 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------------------------------ 13 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------Cache Status------------ 14 | INFO main com.louis.mybatis.test.SelectDemo3 - EmployeesMapper:3 15 | INFO main com.louis.mybatis.test.SelectDemo3 - DepartmentsMapper:0 16 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.DepartmentsMapper:0 17 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.EmployeesMapper:3 18 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------------------------------ 19 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------Cache Status------------ 20 | INFO main com.louis.mybatis.test.SelectDemo3 - EmployeesMapper:4 21 | INFO main com.louis.mybatis.test.SelectDemo3 - DepartmentsMapper:0 22 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.DepartmentsMapper:0 23 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.EmployeesMapper:4 24 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------------------------------ 25 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------Cache Status------------ 26 | INFO main com.louis.mybatis.test.SelectDemo3 - EmployeesMapper:0 27 | INFO main com.louis.mybatis.test.SelectDemo3 - DepartmentsMapper:1 28 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.DepartmentsMapper:1 29 | INFO main com.louis.mybatis.test.SelectDemo3 - com.louis.mybatis.dao.EmployeesMapper:0 30 | INFO main com.louis.mybatis.test.SelectDemo3 - ------------------------------------ 31 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.luanlouis.mybatis.plugin 6 | mybatis-enhanced-cache 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | cache-plugin 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | org.mybatis 20 | mybatis 21 | 3.2.7 22 | 23 | 24 | 25 | log4j 26 | log4j 27 | 1.2.17 28 | true 29 | 30 | 31 | org.apache.logging.log4j 32 | log4j-core 33 | 2.0-rc1 34 | true 35 | 36 | 37 | commons-logging 38 | commons-logging 39 | 1.1.1 40 | true 41 | test 42 | 43 | 44 | com.oracle 45 | ojdbc14 46 | 10.2.0.4.0 47 | test 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main/java/org/luanlouis/mybatis/plugin/cache/CacheKeysPool.java: -------------------------------------------------------------------------------- 1 | package org.luanlouis.mybatis.plugin.cache; 2 | 3 | import java.util.HashSet; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.Set; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | 10 | 11 | /** 12 | * CacheKey缓冲池 13 | * 记录MyBatis的每次查询的查询所产生的CacheKey 14 | * 对于由某个特定的statementId 所执行的查询,其查询所产生的CacheKey存放到一个Set集合中, 15 | * 以>的形式存储到CacheKey缓冲池中 16 | * @author louluan 17 | * @date 2014-12-5 18 | */ 19 | public class CacheKeysPool 20 | { 21 | private Map> pool = new ConcurrentHashMap>(); 22 | 23 | public Set get(String key) 24 | { 25 | if(pool.get(key)==null) 26 | { 27 | pool.put(key, new HashSet()); 28 | } 29 | return pool.get(key); 30 | } 31 | 32 | public Set put(String key,Set value) 33 | { 34 | return pool.put(key, value); 35 | } 36 | 37 | public void putElement(String key,Object element) 38 | { 39 | if(pool.get(key)==null) 40 | { 41 | pool.put(key, new HashSet()); 42 | } 43 | pool.get(key).add(element); 44 | } 45 | 46 | public Set remove(String key) 47 | { 48 | return pool.remove(key); 49 | } 50 | 51 | public void clear() 52 | { 53 | pool.clear(); 54 | } 55 | 56 | public Set keySet() 57 | { 58 | return pool.keySet(); 59 | } 60 | 61 | public Map> getOriginalPool() 62 | { 63 | return pool; 64 | } 65 | 66 | public Set>> entrySet() 67 | { 68 | return this.pool.entrySet(); 69 | } 70 | 71 | public void putAll(CacheKeysPool pool) 72 | { 73 | for(Entry> entry : pool.entrySet()) 74 | { 75 | for(Object item:entry.getValue()) 76 | { 77 | this.putElement(entry.getKey(), item); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/luanlouis/mybatis/plugin/cache/EnhancedCachingExecutor.java: -------------------------------------------------------------------------------- 1 | package org.luanlouis.mybatis.plugin.cache; 2 | 3 | 4 | import java.util.HashSet; 5 | import java.util.Properties; 6 | import java.util.Set; 7 | 8 | import org.apache.ibatis.cache.CacheKey; 9 | import org.apache.ibatis.executor.Executor; 10 | import org.apache.ibatis.mapping.BoundSql; 11 | import org.apache.ibatis.mapping.MappedStatement; 12 | import org.apache.ibatis.plugin.Interceptor; 13 | import org.apache.ibatis.plugin.Intercepts; 14 | import org.apache.ibatis.plugin.Invocation; 15 | import org.apache.ibatis.plugin.Plugin; 16 | import org.apache.ibatis.plugin.Signature; 17 | import org.apache.ibatis.session.ResultHandler; 18 | import org.apache.ibatis.session.RowBounds; 19 | import org.luanlouis.mybatis.plugin.cache.impl.EnhancedCachingManagerImpl; 20 | 21 | 22 | 23 | @Intercepts(value = { 24 | @Signature(args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}, method = "query", type = Executor.class), 25 | @Signature(args = {MappedStatement.class, Object.class}, method = "update", type = Executor.class), 26 | @Signature(args = {boolean.class}, method = "commit", type = Executor.class), 27 | @Signature(args = {boolean.class}, method = "rollback", type = Executor.class), 28 | @Signature(args = {boolean.class}, method = "close", type = Executor.class) 29 | }) 30 | public class EnhancedCachingExecutor implements Interceptor { 31 | private CacheKeysPool queryCacheOnCommit = new CacheKeysPool(); 32 | private Set updateStatementOnCommit = new HashSet(); 33 | EnhancedCachingManager cachingManager = EnhancedCachingManagerImpl.getInstance(); 34 | 35 | public Object intercept(Invocation invocation) throws Throwable { 36 | String name = invocation.getMethod().getName(); 37 | Object result =null; 38 | if("query".equals(name)) 39 | { 40 | result = this.processQuery(invocation); 41 | } 42 | else if("update".equals(name)) 43 | { 44 | result = this.processUpdate(invocation); 45 | } 46 | else if("commit".equals(name)) 47 | { 48 | result = this.processCommit(invocation); 49 | } 50 | else if("rollback".equals(name)) 51 | { 52 | result = this.processRollback(invocation); 53 | } 54 | else if("close".equals(name)) 55 | { 56 | result = this.processClose(invocation); 57 | } 58 | return result; 59 | } 60 | 61 | public Object plugin(Object target) { 62 | return Plugin.wrap(target, this); 63 | } 64 | 65 | /** 66 | * when executing a query operation 67 | * 1. record this statement's id and it's corresponding Cache Object into Global Caching Manager; 68 | * 2. record this statement's id and 69 | * @param invocation 70 | * @return 71 | * @throws Throwable 72 | */ 73 | protected Object processQuery(Invocation invocation) throws Throwable { 74 | Object result = invocation.proceed(); 75 | if(cachingManager.isCacheEnabled()) 76 | { 77 | Object[] args = invocation.getArgs(); 78 | MappedStatement mappedStatement = (MappedStatement)args[0]; 79 | 80 | //如果本条statementId表示的查询语句配置了 flushCache=true,则清空querCacheOnCommit缓存 81 | if(mappedStatement.isFlushCacheRequired()) 82 | { 83 | queryCacheOnCommit.clear(); 84 | } 85 | //如果本条statementId表示的查询语句配置了使用缓存,并且二级缓存不为空,则将StatementId 和对应的二级缓存对象映射关系添加到全局缓存映射管理器中 86 | if(mappedStatement.isUseCache() && mappedStatement.getCache()!=null) 87 | { 88 | cachingManager.appendStatementCacheMap(mappedStatement.getId(), mappedStatement.getCache()); 89 | } 90 | 91 | Object parameter = args[1]; 92 | RowBounds rowBounds = (RowBounds)args[2]; 93 | Executor executor = (Executor)invocation.getTarget(); 94 | BoundSql boundSql = mappedStatement.getBoundSql(parameter); 95 | 96 | //记录本次查询所产生的CacheKey 97 | CacheKey cacheKey= executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql); 98 | queryCacheOnCommit.putElement(mappedStatement.getId(), cacheKey); 99 | } 100 | 101 | return result; 102 | } 103 | 104 | protected Object processUpdate(Invocation invocation) throws Throwable { 105 | 106 | Object result = invocation.proceed(); 107 | MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; 108 | updateStatementOnCommit.add(mappedStatement.getId()); 109 | return result; 110 | } 111 | 112 | protected Object processCommit(Invocation invocation) throws Throwable { 113 | Object result = invocation.proceed(); 114 | refreshCache(); 115 | return result; 116 | } 117 | 118 | protected Object processRollback(Invocation invocation) throws Throwable { 119 | Object result = invocation.proceed(); 120 | clearSessionData(); 121 | return result; 122 | } 123 | 124 | protected Object processClose(Invocation invocation) throws Throwable { 125 | Object result = invocation.proceed(); 126 | boolean forceRollback = (Boolean) invocation.getArgs()[0]; 127 | if(forceRollback) 128 | { 129 | clearSessionData(); 130 | } 131 | else 132 | { 133 | refreshCache(); 134 | } 135 | return result; 136 | } 137 | 138 | 139 | 140 | /** 141 | * when the sqlSession has been committed,rollbacked,or closed, 142 | * session buffer query CacheKeys and update Statement collections should be cleared. 143 | * 144 | * 当SqlSession 执行了commit()、rollback()、close()方法, 145 | * Session级别的查询语句产生的CacheKey集合以及 执行的更新语句集合应该被清空 146 | */ 147 | private synchronized void clearSessionData() 148 | { 149 | queryCacheOnCommit.clear(); 150 | updateStatementOnCommit.clear(); 151 | } 152 | 153 | 154 | 155 | /** 156 | * refresh the session cache,there are two things have to do: 157 | * 1. add this session scope query logs to global cache Manager 158 | * 2. clear the related caches according to the update statements as configured in "dependency" file 159 | * 3. clear the session data 160 | */ 161 | private synchronized void refreshCache() 162 | { 163 | cachingManager.refreshCacheKey(queryCacheOnCommit); 164 | // clear the related caches 165 | cachingManager.clearRelatedCaches(updateStatementOnCommit); 166 | clearSessionData(); 167 | } 168 | 169 | /** 170 | * 171 | * 172 | * Executor插件配置信息加载点 173 | * properties中有 "dependency" 属性来指示 配置的缓存依赖配置信息,读取文件,初始化EnhancedCacheManager 174 | */ 175 | public void setProperties(Properties properties) { 176 | 177 | if(!cachingManager.isInitialized()) 178 | { 179 | cachingManager.initialize(properties); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/org/luanlouis/mybatis/plugin/cache/EnhancedCachingManager.java: -------------------------------------------------------------------------------- 1 | package org.luanlouis.mybatis.plugin.cache; 2 | 3 | import java.util.Properties; 4 | import java.util.Set; 5 | 6 | import org.apache.ibatis.cache.Cache; 7 | 8 | 9 | /** 10 | * Global Cache Manager for MyBatis 11 | * manages all Secondary Caches in MyBatis 12 | * 13 | * 14 | * MyBatis 全局二级缓存管理器 15 | * 负责管理MyBatis内部的所有二级缓存Cache 16 | * 该对象应该是 单例模式,一个MyBatis应用上下文中应该只有一个实例对象 17 | * 18 | * 该管理器维护着MyBatis所有的查询所产生的CacheKey集合,当有update 操作执行时,会根据此update操作对应的StatementId,查看此StatementId是否指定了要刷新的查询缓存,然后指定此 19 | * 20 | * 21 | * @author louluan 22 | * @date 2014-12-5 23 | */ 24 | public interface EnhancedCachingManager { 25 | 26 | 27 | public boolean isInitialized(); 28 | 29 | /** 30 | * MyBatis是否开启了二级缓存,即 31 | * @return 32 | */ 33 | public boolean isCacheEnabled(); 34 | 35 | /** 36 | * 37 | * 初始化 缓存管理器,应该只被调用一次; 38 | * 39 | * @param properties 40 | * properties 中至少包含两个属性: 41 | * dependency : 该值表示着缓存依赖配置文件的位置 42 | * cacheEnbled : "true" or "false",该配置必须要与的值保持一致 43 | */ 44 | public void initialize(Properties properties); 45 | 46 | /** 47 | * 将Session会话级别产生的CacheKey缓冲池中的数据 更新到全局CacheKey缓冲池中 48 | * @param sessionCacheKeysPool 49 | */ 50 | public void refreshCacheKey(CacheKeysPool sessionCacheKeysPool); 51 | 52 | /** 53 | * 整个插件的核心,根据指定的statementId更新与之关联的二级缓存 54 | * 传入的StatementId集合是由会话级别的update语句对应的StatementId, 55 | * EnhancedCachingManager将通过查询相应的StatementId去查看是否配置了依赖关系,如果有,则将依赖关系中的statementId的查询缓存全部清空 56 | * @param set 57 | */ 58 | public void clearRelatedCaches(Set set); 59 | 60 | 61 | /** 62 | * 如果MyBatis开启了二级缓存,并且本查询的StatementId使用了二级缓存,并且对应的Mapper配置了缓存, 63 | * 则将此StatementId和Cache已键值对的形式存储到全局缓存管理器中 64 | * @param statementId 65 | * @param cache 66 | */ 67 | public void appendStatementCacheMap(String statementId,Cache cache); 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/luanlouis/mybatis/plugin/cache/dependency.dtd: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /src/main/java/org/luanlouis/mybatis/plugin/cache/impl/EnhancedCachingManagerImpl.java: -------------------------------------------------------------------------------- 1 | package org.luanlouis.mybatis.plugin.cache.impl; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Properties; 9 | import java.util.Set; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | import org.apache.ibatis.cache.Cache; 13 | import org.apache.ibatis.io.Resources; 14 | import org.apache.ibatis.parsing.XNode; 15 | import org.apache.ibatis.parsing.XPathParser; 16 | import org.luanlouis.mybatis.plugin.cache.CacheKeysPool; 17 | import org.luanlouis.mybatis.plugin.cache.EnhancedCachingManager; 18 | 19 | public class EnhancedCachingManagerImpl implements EnhancedCachingManager{ 20 | 21 | //每一个statementId 更新依赖的statementId集合 22 | private Map> observers=new ConcurrentHashMap>(); 23 | 24 | //全局性的 statemntId与CacheKey集合 25 | private CacheKeysPool sharedCacheKeysPool = new CacheKeysPool(); 26 | //记录每一个statementId 对应的Cache 对象 27 | private Map holds = new ConcurrentHashMap(); 28 | private boolean initialized = false; 29 | private boolean cacheEnabled = false; 30 | 31 | private static EnhancedCachingManagerImpl enhancedCacheManager; 32 | 33 | private EnhancedCachingManagerImpl(){} 34 | public static EnhancedCachingManagerImpl getInstance() 35 | { 36 | return enhancedCacheManager==null ? (enhancedCacheManager =new EnhancedCachingManagerImpl()):enhancedCacheManager; 37 | } 38 | 39 | public void refreshCacheKey(CacheKeysPool keysPool) { 40 | sharedCacheKeysPool.putAll(keysPool); 41 | //sharedCacheKeysPool.display(); 42 | } 43 | public void clearRelatedCaches(final Set set) { 44 | //sharedCacheKeysPool.display(); 45 | for(String observable:set) 46 | { 47 | Set relatedStatements = observers.get(observable); 48 | for(String statementId:relatedStatements) 49 | { 50 | Cache cache = holds.get(statementId); 51 | Set cacheKeys = sharedCacheKeysPool.get(statementId); 52 | for(Object cacheKey: cacheKeys) 53 | { 54 | cache.removeObject(cacheKey); 55 | } 56 | } 57 | // clear shared cacheKey Pool width specific key 58 | sharedCacheKeysPool.remove(observable); 59 | } 60 | } 61 | public boolean isInitialized() { 62 | return initialized; 63 | } 64 | 65 | public void initialize(Properties properties) 66 | { 67 | String dependency = properties.getProperty("dependency"); 68 | if(!("".equals(dependency) || dependency==null)) 69 | { 70 | InputStream inputStream; 71 | try 72 | { 73 | inputStream = Resources.getResourceAsStream(dependency); 74 | XPathParser parser = new XPathParser(inputStream); 75 | List statements = parser.evalNodes("/dependencies/statements/statement"); 76 | for(XNode node :statements) 77 | { 78 | Set temp = new HashSet(); 79 | List obs = node.evalNodes("observer"); 80 | for(XNode observer:obs) 81 | { 82 | temp.add(observer.getStringAttribute("id")); 83 | } 84 | this.observers.put(node.getStringAttribute("id"), temp); 85 | } 86 | initialized = true; 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | //cacheEnabled 93 | String cacheEnabled = properties.getProperty("cacheEnabled", "true"); 94 | if("true".equals(cacheEnabled)) 95 | { 96 | this.cacheEnabled = true; 97 | } 98 | } 99 | 100 | public void appendStatementCacheMap(String statementId, Cache cache) { 101 | if(holds.containsKey(statementId)&& holds.get(statementId)!=null) 102 | { 103 | return ; 104 | } 105 | holds.put(statementId, cache); 106 | } 107 | 108 | public boolean isCacheEnabled() { 109 | return cacheEnabled; 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/test/java/com/louis/mybatis/domain/DepartmentsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID 17 | 18 | 19 | 20 | update HR.DEPARTMENTS 21 | set DEPARTMENT_NAME = #{departmentName,jdbcType=VARCHAR}, 22 | MANAGER_ID = #{managerId,jdbcType=DECIMAL}, 23 | LOCATION_ID = #{locationId,jdbcType=DECIMAL} 24 | where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL} 25 | 26 | 32 | -------------------------------------------------------------------------------- /src/test/java/com/louis/mybatis/domain/EmployeesMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY, 23 | COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID 24 | 25 | 26 | 27 | 33 | 34 | 40 | 41 | 49 | 50 | -------------------------------------------------------------------------------- /src/test/java/com/louis/mybatis/model/Department.java: -------------------------------------------------------------------------------- 1 | package com.louis.mybatis.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public class Department implements Serializable{ 6 | /** 7 | * 8 | */ 9 | private static final long serialVersionUID = 1L; 10 | 11 | private Short departmentId; 12 | 13 | private String departmentName; 14 | 15 | private Integer managerId; 16 | 17 | private Short locationId; 18 | 19 | public Short getDepartmentId() { 20 | return departmentId; 21 | } 22 | 23 | public void setDepartmentId(Short departmentId) { 24 | this.departmentId = departmentId; 25 | } 26 | 27 | public String getDepartmentName() { 28 | return departmentName; 29 | } 30 | 31 | public void setDepartmentName(String departmentName) { 32 | this.departmentName = departmentName; 33 | } 34 | 35 | public Integer getManagerId() { 36 | return managerId; 37 | } 38 | 39 | public void setManagerId(Integer managerId) { 40 | this.managerId = managerId; 41 | } 42 | 43 | public Short getLocationId() { 44 | return locationId; 45 | } 46 | 47 | public void setLocationId(Short locationId) { 48 | this.locationId = locationId; 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/com/louis/mybatis/model/Employee.java: -------------------------------------------------------------------------------- 1 | package com.louis.mybatis.model; 2 | 3 | import java.io.Serializable; 4 | import java.math.BigDecimal; 5 | import java.util.Date; 6 | 7 | public class Employee implements Serializable{ 8 | /** 9 | * 10 | */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | private Integer employeeId; 14 | 15 | private String firstName; 16 | 17 | private String lastName; 18 | 19 | private String email; 20 | 21 | private String phoneNumber; 22 | 23 | private Date hireDate; 24 | 25 | private String jobId; 26 | 27 | private BigDecimal salary; 28 | 29 | private BigDecimal commissionPct; 30 | 31 | private Integer managerId; 32 | 33 | private Short departmentId; 34 | 35 | public Integer getEmployeeId() { 36 | return employeeId; 37 | } 38 | 39 | public void setEmployeeId(Integer employeeId) { 40 | this.employeeId = employeeId; 41 | } 42 | 43 | public String getFirstName() { 44 | return firstName; 45 | } 46 | 47 | public void setFirstName(String firstName) { 48 | this.firstName = firstName; 49 | } 50 | 51 | public String getLastName() { 52 | return lastName; 53 | } 54 | 55 | public void setLastName(String lastName) { 56 | this.lastName = lastName; 57 | } 58 | 59 | public String getEmail() { 60 | return email; 61 | } 62 | 63 | public void setEmail(String email) { 64 | this.email = email; 65 | } 66 | 67 | public String getPhoneNumber() { 68 | return phoneNumber; 69 | } 70 | 71 | public void setPhoneNumber(String phoneNumber) { 72 | this.phoneNumber = phoneNumber; 73 | } 74 | 75 | public Date getHireDate() { 76 | return hireDate; 77 | } 78 | 79 | public void setHireDate(Date hireDate) { 80 | this.hireDate = hireDate; 81 | } 82 | 83 | public String getJobId() { 84 | return jobId; 85 | } 86 | 87 | public void setJobId(String jobId) { 88 | this.jobId = jobId; 89 | } 90 | 91 | public BigDecimal getSalary() { 92 | return salary; 93 | } 94 | 95 | public void setSalary(BigDecimal salary) { 96 | this.salary = salary; 97 | } 98 | 99 | public BigDecimal getCommissionPct() { 100 | return commissionPct; 101 | } 102 | 103 | public void setCommissionPct(BigDecimal commissionPct) { 104 | this.commissionPct = commissionPct; 105 | } 106 | 107 | public Integer getManagerId() { 108 | return managerId; 109 | } 110 | 111 | public void setManagerId(Integer managerId) { 112 | this.managerId = managerId; 113 | } 114 | 115 | public Short getDepartmentId() { 116 | return departmentId; 117 | } 118 | 119 | public void setDepartmentId(Short departmentId) { 120 | this.departmentId = departmentId; 121 | } 122 | } -------------------------------------------------------------------------------- /src/test/java/com/louis/mybatis/test/SelectDemo3.java: -------------------------------------------------------------------------------- 1 | package com.louis.mybatis.test; 2 | 3 | import java.io.InputStream; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.apache.ibatis.io.Resources; 11 | import org.apache.ibatis.session.SqlSession; 12 | import org.apache.ibatis.session.SqlSessionFactory; 13 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 14 | import org.apache.log4j.Logger; 15 | 16 | import com.louis.mybatis.model.Department; 17 | import com.louis.mybatis.model.Employee; 18 | 19 | /** 20 | * SqlSession 简单查询演示类 21 | * @author louluan 22 | */ 23 | public class SelectDemo3 { 24 | 25 | private static final Logger loger = Logger.getLogger(SelectDemo3.class); 26 | 27 | public static void main(String[] args) throws Exception { 28 | InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml"); 29 | SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 30 | SqlSessionFactory factory = builder.build(inputStream); 31 | 32 | SqlSession sqlSession = factory.openSession(true); 33 | SqlSession sqlSession2 = factory.openSession(true); 34 | //3.使用SqlSession查询 35 | Map params = new HashMap(); 36 | params.put("employeeId",10); 37 | //a.查询工资低于10000的员工 38 | Date first = new Date(); 39 | //第一次查询 40 | List result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params); 41 | sqlSession.commit(); 42 | checkCacheStatus(sqlSession); 43 | params.put("employeeId", 11); 44 | result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params); 45 | sqlSession.commit(); 46 | checkCacheStatus(sqlSession); 47 | params.put("employeeId", 12); 48 | result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params); 49 | sqlSession.commit(); 50 | checkCacheStatus(sqlSession); 51 | params.put("employeeId", 13); 52 | result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params); 53 | sqlSession.commit(); 54 | checkCacheStatus(sqlSession); 55 | Department department = sqlSession.selectOne("com.louis.mybatis.dao.DepartmentsMapper.selectByPrimaryKey",10); 56 | department.setDepartmentName("updated"); 57 | sqlSession2.update("com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey", department); 58 | sqlSession.commit(); 59 | checkCacheStatus(sqlSession); 60 | } 61 | 62 | 63 | public static void checkCacheStatus(SqlSession sqlSession) 64 | { 65 | loger.info("------------Cache Status------------"); 66 | Iterator iter = sqlSession.getConfiguration().getCacheNames().iterator(); 67 | while(iter.hasNext()) 68 | { 69 | String it = iter.next(); 70 | loger.info(it+":"+sqlSession.getConfiguration().getCache(it).getSize()); 71 | } 72 | loger.info("------------------------------------"); 73 | 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/dependencys.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/java/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO,CONSOLE,R 2 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender 3 | log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.CONSOLE.layout.ConversionPattern=%d [%t] %-5p %c - %m%n 5 | log4j.appender.R=org.apache.log4j.RollingFileAppender 6 | log4j.appender.R.File=example.log 7 | log4j.appender.R.MaxFileSize=100KB 8 | log4j.appender.R.MaxBackupIndex=1 9 | log4j.appender.R.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n 11 | -------------------------------------------------------------------------------- /src/test/java/mybatisConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------