《深入理解mybatis原理(六)》 MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存

Mybatis缓存机制与性能测试指南 本文还有配套的精品资源,点击获取 简介:Mybatis,一个流行的Java持久层框架,通过缓存机制优化数据库操作。本文将深入剖析Mybatis的两级缓存体系,一级缓存和二级缓存的工作原理,以及它们在实际项目中的应用和测试方法。通过测试关键点,如缓存配置、读写、清除、并发控制和性能评估,开发者可以深入理解并掌握Mybatis缓存的高级用法,从而提升应用的性能和稳定性。 ... 阅读详情

前几天网友chanfish 给我抛出了一个问题,笼统地讲就是如何能细粒度地控制MyBatis的二级缓存问题,酝酿了几天,觉得可以写个插件来实现这个这一功能。本文就是从问题入手,一步步分析现存的MyBatis的二级缓存的不足之处,探讨一点可以改进的地方,并且对不足之处开发一个插件进行弥补。

本文如下组织结构:

  1. 一个关于MyBatis的二级缓存的实际问题
  2. 当前MyBatis二级缓存的工作机制
  3. mybatis-enhanced-cache插件的设计和工作原理
  4. mybatis-enhanced-cache 插件的使用实例

1.一个关于MyBatis的二级缓存的实际问题

网友chanfish 给我抛出的问题

现有AMapper.xml中定义了对数据库表ATable 的CRUD操作,BMapper定义了对数据库表BTable的CRUD操作;
假设 MyBatis 的二级缓存开启,并且 AMapper 中使用了二级缓存,AMapper对应的二级缓存为ACache;
除此之外,AMapper 中还定义了一个跟BTable有关的查询语句,类似如下所述:

[html]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  1. <select id="selectATableWithJoin" resultMap="BaseResultMap" useCache="true">  
  2.       select * from ATable left join BTable on ....  
  3. </select>  
[html]  view plain  copy
  1. <select id="selectATableWithJoin" resultMap="BaseResultMap" useCache="true">  
  2.       select * from ATable left join BTable on ....  
  3. </select>  

执行以下操作:

1. 执行AMapper中的"selectATableWithJoin" 操作,此时会将查询到的结果放置到AMapper对应的二级缓存ACache中;

2. 执行BMapper中对BTable的更新操作(update、delete、insert)后,BTable的数据更新;

3. 再执行1完全相同的查询,这时候会直接从AMapper二级缓存ACache中取值,将ACache中的值直接返回;


好,问题就出现在第3步上:

     由于AMapper的“selectATableWithJoin” 对应的SQL语句需要和BTable进行join查找,而在第 2 步BTable的数据已经更新了,但是第 3 步查询的值是第 1 步的缓存值,已经极有可能跟真实数据库结果不一样,即ACache中缓存数据过期了!


总结来看,就是:

         对于某些使用了 join连接的查询,如果其关联的表数据发生了更新,join连接的查询由于先前缓存的原因,导致查询结果和真实数据不同步;

从MyBatis的角度来看,这个问题可以这样表述:

        对于某些表执行了更新(update、delete、insert)操作后,如何去清空跟这些表有关联的查询语句所造成的缓存;


当前的MyBatis的缓存机制不能很好地处理这一问题,下面我们将从当前的MyBatis的缓存机制入手,分析这一问题:


2. 当前MyBatis二级缓存的工作机制:

当前MyBatis二级缓存的工作机制:

MyBatis二级缓存的一个重要特点:即松散的Cache缓存管理和维护。

     一个Mapper中定义的增删改查操作只能影响到自己关联的Cache对象。如上图所示的Mapper namespace1中定义的若干CRUD语句,产生的缓存只会被放置到相应关联的Cache1中,即Mapper namespace2,namespace3,namespace4 中的CRUD的语句不会影响到Cache1。

     可以看出,Mapper之间的缓存关系比较松散,相互关联的程度比较弱。

     

     现在再回到上面描述的问题,如果我们将AMapper和BMapper共用一个Cache对象,那么,当BMapper执行更新操作时,可以清空对应Cache中的所有的缓存数据,这样的话,数据不是也可以保持最新吗?

    确实这个也是一种解决方案,不过,它会使缓存的使用效率变的很低!AMapper和BMapper的任意的更新操作都会将共用的Cache清空,会频繁地清空Cache,导致Cache实际的命中率和使用率就变得很低了,所以这种策略实际情况下是不可取的。


     最理想的解决方案就是:

          对于某些表执行了更新(update、delete、insert)操作后,如何去清空跟这些表有关联的查询语句所造成的缓存;

     这样,就是以很细的粒度管理MyBatis内部的缓存,使得缓存的使用率和准确率都能大大地提升。   

    基于这个思路,我写了一个对应的mybatis-enhanced-cache 缓存插件,可以很好地支持上述的功能。  

    对于上述的例子中,该插件可以实现:当BMapper对BTable执行了更新操作时,指定清除与BTable相关联的selectATableWithJoin查询语句在ACache中产生的缓存。

     

     接下来就来看看这个mybatis-enhanced-cache插件的设计原理吧:



3.mybatis-enhanced-cache插件的设计和工作原理


mybatis-enhanced-cache插件的设计和工作原理

该插件主要由两个构件组成:EnhancedCachingExecutor和EnhancedCachingManager。

EnhancedCachingExecutor是针对于Executor的拦截器,拦截Executor的几个关键的方法;

EnhancedCachingExecutor主要做以下几件事:

1. 每当有Executor执行query操作时,

       1.1  记录下该查询StatementId和CacheKey,然后将其添加到EnhancedCachingManager中;

       1.2  记录下该查询StatementId 和此StatementId所属Mapper内的Cache缓存对象引用,添加到EnhancedCachingManager中;

2. 每当Executor执行了update操作时,将此 update操作的StatementId传递给EnhancedCachingManager,让EnhancedCachingManager根据此update的StatementId的配置,去清空指定的查询语句所产生的缓存;


另一个构件:EnhancedCachingManager,它也是本插件的核心,它维护着以下几样东西:

1. 整个MyBatis的所有查询所产生的CacheKey集合(以statementId分类);

2. 所有的使用过了的查询的statementId 及其对应的Cache缓存对象的引用;

3. update类型的StatementId和查询StatementId集合的映射,用于当Update类型的语句执行时,根据此映射决定应该清空哪些查询语句产生的缓存;

如下图所示:


工作原理:

           原理很简单,就是 当执行了某个update操作时,根据配置信息去清空指定的查询语句在Cache中所产生的缓存数据。



如何获取mybatis-enhanced-cache插件源码

1. 源码和jar包2合一压缩包

2. github 地址,直接fork即可:

         https://github.com/LuanLouis/mybatis-enhanced-cache



4. mybatis-enhanced-cache 插件的使用实例:

1. 下载 mybatis-enhanced-cache.rar压缩包,解压,将其内的mybatis-enhanced-cache-0.0.1-SNAPSHOT.jar添加到项目的classpath下;


2. 配置MyBatis配置文件如下:

[html]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  1. <plugins>  
  2.      <plugin interceptor="org.luanlouis.mybatis.plugin.cache.EnhancedCachingExecutor">  
  3.         <property name="dependency" value="dependencys.xml"/>  
  4.         <property name="cacheEnabled" value="true"/>  
  5.      </plugin>  
  6. </plugins>  
[html]  view plain  copy
  1. <plugins>  
  2.      <plugin interceptor="org.luanlouis.mybatis.plugin.cache.EnhancedCachingExecutor">  
  3.         <property name="dependency" value="dependencys.xml"/>  
  4.         <property name="cacheEnabled" value="true"/>  
  5.      </plugin>  
  6. </plugins>  

    其中,<property name="dependency"> 中的value属性是 StatementId之间的依赖关系的配置文件路径。


3. 配置StatementId之间的依赖关系

[html]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <dependencies>  
  3.    <statements>  
  4.        <statement id="com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey">  
  5.           <observer id="com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments" />  
  6.        </statement>  
  7.    </statements>  
  8. </dependencies>  
[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <dependencies>  
  3.    <statements>  
  4.        <statement id="com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey">  
  5.           <observer id="com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments" />  
  6.        </statement>  
  7.    </statements>  
  8. </dependencies>  

 <statement>节点配置的是更新语句的statementId,其内的子节点<observer> 配置的是当更新语句执行后,应当清空缓存的查询语句的StatementId。子节点<observer>可以有多个。

 如上的配置,则说明,如果"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey" 更新语句执行后,由 “com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments” 语句所产生的放置在Cache缓存中的数据都都会被清空。


4. 配置DepartmentsMapper.xml 和EmployeesMapper.xml

[html]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  
  3. <mapper namespace="com.louis.mybatis.dao.DepartmentsMapper" >  
  4.      
  5.    <cache></cache>  
  6.   
  7.   <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Department" >  
  8.     <id column="DEPARTMENT_ID" property="departmentId" jdbcType="DECIMAL" />  
  9.     <result column="DEPARTMENT_NAME" property="departmentName" jdbcType="VARCHAR" />  
  10.     <result column="MANAGER_ID" property="managerId" jdbcType="DECIMAL" />  
  11.     <result column="LOCATION_ID" property="locationId" jdbcType="DECIMAL" />  
  12.   </resultMap>  
  13.     
  14.     
  15.   <sql id="Base_Column_List" >  
  16.     DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID  
  17.   </sql>  
  18.     
  19.   <update id="updateByPrimaryKey" parameterType="com.louis.mybatis.model.Department" >  
  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.   </update>  
  26.     <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >  
  27.     select   
  28.     <include refid="Base_Column_List" />  
  29.     from HR.DEPARTMENTS  
  30.     where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}  
  31.   </select>  
  32. </mapper>  
[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8" ?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >  
  3. <mapper namespace="com.louis.mybatis.dao.DepartmentsMapper" >  
  4.      
  5.    <cache></cache>  
  6.   
  7.   <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Department" >  
  8.     <id column="DEPARTMENT_ID" property="departmentId" jdbcType="DECIMAL" />  
  9.     <result column="DEPARTMENT_NAME" property="departmentName" jdbcType="VARCHAR" />  
  10.     <result column="MANAGER_ID" property="managerId" jdbcType="DECIMAL" />  
  11.     <result column="LOCATION_ID" property="locationId" jdbcType="DECIMAL" />  
  12.   </resultMap>  
  13.     
  14.     
  15.   <sql id="Base_Column_List" >  
  16.     DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID  
  17.   </sql>  
  18.     
  19.   <update id="updateByPrimaryKey" parameterType="com.louis.mybatis.model.Department" >  
  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.   </update>  
  26.     <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >  
  27.     select   
  28.     <include refid="Base_Column_List" />  
  29.     from HR.DEPARTMENTS  
  30.     where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}  
  31.   </select>  
  32. </mapper>  
[html]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  3. <mapper namespace="com.louis.mybatis.dao.EmployeesMapper">  
  4.   
  5.   <cache eviction="LRU" flushInterval="100000" size="10000"/>  
  6.   
  7.   <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee">  
  8.     <id column="EMPLOYEE_ID" jdbcType="DECIMAL" property="employeeId" />  
  9.     <result column="FIRST_NAME" jdbcType="VARCHAR" property="firstName" />  
  10.     <result column="LAST_NAME" jdbcType="VARCHAR" property="lastName" />  
  11.     <result column="EMAIL" jdbcType="VARCHAR" property="email" />  
  12.     <result column="PHONE_NUMBER" jdbcType="VARCHAR" property="phoneNumber" />  
  13.     <result column="HIRE_DATE" jdbcType="DATE" property="hireDate" />  
  14.     <result column="JOB_ID" jdbcType="VARCHAR" property="jobId" />  
  15.     <result column="SALARY" jdbcType="DECIMAL" property="salary" />  
  16.     <result column="COMMISSION_PCT" jdbcType="DECIMAL" property="commissionPct" />  
  17.     <result column="MANAGER_ID" jdbcType="DECIMAL" property="managerId" />  
  18.     <result column="DEPARTMENT_ID" jdbcType="DECIMAL" property="departmentId" />  
  19.   </resultMap>  
  20.   
  21.   <sql id="Base_Column_List">  
  22.     EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY,   
  23.     COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID  
  24.   </sql>  
  25.     
  26.   <select id="selectWithDepartments" parameterType="java.lang.Integer" resultMap="BaseResultMap" useCache="true" >  
  27.     select   
  28.     *  
  29.     from HR.EMPLOYEES t left join HR.DEPARTMENTS S ON T.DEPARTMENT_ID = S.DEPARTMENT_ID  
  30.     where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}  
  31.   </select>  
  32.     
  33. </mapper>  
[html]  view plain  copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  3. <mapper namespace="com.louis.mybatis.dao.EmployeesMapper">  
  4.   
  5.   <cache eviction="LRU" flushInterval="100000" size="10000"/>  
  6.   
  7.   <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee">  
  8.     <id column="EMPLOYEE_ID" jdbcType="DECIMAL" property="employeeId" />  
  9.     <result column="FIRST_NAME" jdbcType="VARCHAR" property="firstName" />  
  10.     <result column="LAST_NAME" jdbcType="VARCHAR" property="lastName" />  
  11.     <result column="EMAIL" jdbcType="VARCHAR" property="email" />  
  12.     <result column="PHONE_NUMBER" jdbcType="VARCHAR" property="phoneNumber" />  
  13.     <result column="HIRE_DATE" jdbcType="DATE" property="hireDate" />  
  14.     <result column="JOB_ID" jdbcType="VARCHAR" property="jobId" />  
  15.     <result column="SALARY" jdbcType="DECIMAL" property="salary" />  
  16.     <result column="COMMISSION_PCT" jdbcType="DECIMAL" property="commissionPct" />  
  17.     <result column="MANAGER_ID" jdbcType="DECIMAL" property="managerId" />  
  18.     <result column="DEPARTMENT_ID" jdbcType="DECIMAL" property="departmentId" />  
  19.   </resultMap>  
  20.   
  21.   <sql id="Base_Column_List">  
  22.     EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY,   
  23.     COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID  
  24.   </sql>  
  25.     
  26.   <select id="selectWithDepartments" parameterType="java.lang.Integer" resultMap="BaseResultMap" useCache="true" >  
  27.     select   
  28.     *  
  29.     from HR.EMPLOYEES t left join HR.DEPARTMENTS S ON T.DEPARTMENT_ID = S.DEPARTMENT_ID  
  30.     where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}  
  31.   </select>  
  32.     
  33. </mapper>  


5. 测试代码:

[java]  view plain  copy
 
在CODE上查看代码片 派生到我的代码片 在CODE上查看代码片 派生到我的代码片
  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<String,Object> params = new HashMap<String,Object>();  
  36.         params.put("employeeId",10);  
  37.         //a.查询工资低于10000的员工  
  38.         Date first = new Date();  
  39.         //第一次查询  
  40.         List<Employee> 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<String> 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. }  
[java]  view plain  copy
  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<String,Object> params = new HashMap<String,Object>();  
  36.         params.put("employeeId",10);  
  37.         //a.查询工资低于10000的员工  
  38.         Date first = new Date();  
  39.         //第一次查询  
  40.         List<Employee> 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<String> 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. }  

结果输出:

结果分析:

从上述的结果可以看出,前四次执行了“com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments”语句,EmployeesMapper对应的Cache缓存中存储的结果缓存有1个增加到4个。

当执行了"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey"后,EmployeeMapper对应的缓存Cache结果被清空了,即"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey"更新语句引起了EmployeeMapper中的"com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments"缓存的清空。





           


作者的话

该插件的实现周期比较短,尚未经过性能方面的测试,如果果您对此插件有任何意见或者看法,可以留言一起交流和探讨。

该插件源码已经放到了Github上,可供大家自由修改,github地址:

                     https://github.com/LuanLouis/mybatis-enhanced-cache


本文转载自:http://blog.youkuaiyun.com/luanlouis/article/details/41800511

mybatis-enhanced-cache源码和jar包 mybatis缓存增强插件,提供细粒度管理缓存的功能 立即下载

相关推荐

FOC实战笔记:RC吸收电路的设计考量与参数调优

本文深入探讨了FOC驱动器中RC吸收电路的设计与调优。针对MOS管开关过程中的电压尖峰问题,详细阐述了RC电路的钳位与能量吸收原理,并提供了从参数计算、实验调试到布局布线的完整实战指南,旨在帮助工程师有效提升系统可靠性与稳定性。

weixin_29219539的博客 276

如何细粒度地控制你的MyBatis二级缓存(mybatis-enhanced-cache插件实现)

本文介绍如何细粒度地控制你的MyBatis二级缓存,以及对应的mybatis-enhanced-cache插件实现

我的程序人生(亦山札记) 2万+

【SAP-MDG】工作流任务配置实战:从基础配置到代理分配详解

本文详细解析了SAP MDG工作流任务配置的全流程,从基础配置到代理分配的关键操作。通过实战案例和最佳实践,帮助用户掌握Configure Workflow Tasks的核心技巧,包括环境检查、代理分配、SWDD事务码使用及常见问题排查,提升主数据管理效率。

weixin_33738555的博客 250

两级缓存,像子弹一样飞…

文章目录一、前言二、MyBatis缓存总述三、Mybatis一级缓存3.1 一级缓存3.2 SqlSession中的一级缓存组织结构3.3 一级缓存的生命周期3.4 SqlSession一级缓存的工作流程(构建cacheKey+根据cacheKey查询,已完成)3.5 一级缓存底层设计3.5.1 CacheKey的两个作用3.5.2 CacheKey如何确定?怎样判断某两次查询是完全相同的查询?3.5.3 CacheKey的构建算法(BaseExecutor类中的createCacheKey()方法)3.5

毛毛的专栏 1861

独立站SaaS平台源码搭建全流程指南:从零到部署

一、什么是独立站SaaS?二、搭建前的准备工作技术选型建议模块 推荐方案 备选方案前端框架 React/Vue3 + TypeScript Angular后端框架 Spring Boot (Java)/Django (Python) Node.js (NestJS)数据库 MySQL + Redis PostgreSQL/MongoDB云服务 阿里云/腾讯云/AWS 华为云容器化 Docker + Kubernetes Docker Compose环境要求。

Vx18664956126的博客 1893

硬件开发实践:高速 PCB 差分信号与时钟同步优化

由两条互补信号线组成(正负极)接收端只关注两线之间电压差共模噪声对两线同时作用,可被抵消。

2501_93174775的博客 394

MyBatis Enhanced Cache: 提升数据缓存效率的新选择

MyBatis Enhanced Cache: 提升数据缓存效率的新选择 在Java开发中,MyBatis是一个深受喜爱的持久层框架,它提供了灵活的SQL映射和对象关系映射功能。然而,在处理大规模数据时,高效的缓存策略是必不可少的。为此,我们引入了一个增强型的MyBatis缓存插件——。本文将深入探讨其技术原理、应用场景及特性,帮助开发者们更好地利用这一工具提升应用程序性能。 项目简介 MyBat...

gitblog_00056的博客 554

ABB AC500 系列 PLC 与上位机iFix 的通讯配置

在 软件中,有 3 种方式可以进入 IP config tool 的配置界面 双击 IP_settings,点击 IP config tool,即可进入 IP config tool 界面点击菜单栏的 Tool→IP config如果单独安装了 ABB IP Configration,则可以通过开始→程序→ABB→ABB IPConfigration 启动 IP 配置工具启动 IP config tool 后,即可进入 IP 配置界面。

xdpcxq的专栏 4032

MyBatis3框架详解(六)

aaa

m0_37671741的博客 477

【ROS2】机械臂MoveIt2

navigation2 专注于移动机器人的导航与定位,处理大范围空间的底盘运动;MoveIt2 专注于机械臂的操作规划,处理多关节协同的末端执行器运动。在复合机器人(如,移动机械臂)navigation2 负责移动到目标区域,MoveIt2 负责机械臂抓取。官网:https://moveit.picknik.ai/main/index.html。

郭老二 603

怎样让你的论文看起来“像人写的”?只用三招直接去除AI味(附提示词)

当然如果你在实际使用中还有其他需求,比如降重润色、改写翻译等,也可以结合七哥的专业写作平台来辅助,这个高级学术平台已经内置了ChatGPT4o、ChatGPT4.1、Claude3.7、满血版DeepSeek-R1等AI大模型,帮你写文献综述、干课题申报、写论文等学术任务绝对每一点问题。比如我自己的一套学术写作平台,其中的“降重与润色”模块整合了多种GPTs应用,除了能帮助调整语言风格、避免雷同表达,还可以顺带降个重,对于提升学术文本的整体质量还是非常有用的。一、为什么用AI写出来的文章会有AI味?

智能AI让学术科研更容易 1691

mybatis 细粒度控制二级缓存

  本文要解决的问题:细粒度控制mybatis的二级缓存。mybatis的二级缓存的问题:当更新SQL执行时只清除当前SQL所在命名空间(namespace)的缓存。如果存在2个命名空间namespaceA和namespaceB,当namespaceA下执行更新操作时,namespaceB的缓存并不会清除。正常的情况下,这种策略是不会有问题。但是,如果存在关联查询时就有可能出现问题了,例如name...

weixin_30888707的博客 140

用 CDS 作为 Referenced Data Source 开发 SAP Gateway OData 服务,一条更轻的 SEGW 路线

本文介绍了SAP Gateway项目中从传统手工建模到基于CDS View的现代化开发方式转变。主要内容包括: 传统方式需要在SEGW中手动创建Entity Type、Entity Set等元素,并在DPC_EXT中编写大量查询逻辑,存在模型层和业务逻辑重复表达的问题。 7.50版本后,SAP Gateway与ABAP CDS深度集成,可通过SEGW直接引用CDS View作为数据源,自动生成数据模型和SADL读取逻辑,支持完整的OData查询功能。 最佳实践建议通过Consumption View而非直接

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 707

深入理解mybatis原理(六) MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存

前几天网友chanfish 给我抛出了一个问题,笼统地讲就是如何能细粒度地控制MyBatis的二级缓存问题,酝酿了几天,觉得可以写个插件来实现这个这一功能。本文就是从问题入手,一步步分析现存的MyBatis的二级缓存的不足之处,探讨一点可以改进的地方,并且对不足之处开发一个插件进行弥补。 本文如下组织结构: 一个关于MyBatis的二级缓存的实际问题当前MyBatis二级缓

z742182637的博客 833

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

《深入理解Mybatis原理》Mybatis中的缓存实现原理

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

sdgfafg_25的博客 1649

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

mybatis中一个标签中使用两个update更新两个表_深入理解MyBatis原理 如何细粒度地控制你的MyBatis二级缓存...

点击上方“程序开发者社区”关注,选择“设为星标”第一时间送达实用干货前几天网友chanfish:http://blog.youkuaiyun.com/chanfish 给我抛出了一个问题,笼统地讲就是如何能细粒度地控制MyBatis的二级缓存问题,酝酿了几天,觉得可以写个插件来实现这个这一功能。本文就是从问题入手,一步步分析现存的MyBatis的二级缓存的不足之处,探讨一点可以改进的地方,并且对不足...

weixin_39835607的博客 981

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

mybatis时间区间查询_深入理解MyBatis原理 如何细粒度地控制你的MyBatis二级缓存...

点击上方“程序开发者社区”关注,选择“设为星标”第一时间送达实用干货前几天网友chanfish:http://blog.youkuaiyun.com/chanfish 给我抛出了一个问题,笼统地讲就是如何能细粒度地控制MyBatis的二级缓存问题,酝酿了几天,觉得可以写个插件来实现这个这一功能。本文就是从问题入手,一步步分析现存的MyBatis的二级缓存的不足之处,探讨一点可以改进的地方,并且对不足...

weixin_39768083的博客 98

《深入理解mybatis原理》系列文章

如何细粒度地控制你的MyBatis二级缓存(mybatis-enhanced-cache插件实现) 本文介绍如何细粒度地控制你的MyBatis二级缓存,以及对应的mybatis-enhanced-cache插件实现... 《深入理解mybatis原理》 MyBatis缓存机制的设计与实现

xcwll314的专栏 1485

《深入理解Mybatis原理》Mybatis中的缓存实现原理!

每当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话。在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

mifffy_java的博客 919

Mybatis源码解析优秀博文

最近阅读了许久的mybatis源码,小有所悟。同时也发现网上有许多优秀的mybatis源码讲解博文。本人打算把自己阅读过的、觉得不错的一些博文列出来。以此进一步加深对mybatis框架的理解。其实还有一个目的是,本人也想写一系列博文来分享自己对mybatis的理解。然而。个人文笔太差,列出的这些别人的文章我也会细心研读,并希望在阅读这些文章的同时既加深我对mybatis的理解,又能探索出一种更加通俗

lhever_的博客 3万+

Mybatis源码解析优秀博文汇总

最近阅读了许久的mybatis源码,小有所悟。同时也发现网上有许多优秀的mybatis源码讲解博文。本人打算把自己阅读过的、觉得不错的一些博文列出来。以此进一步加深对mybatis框架的理解。其实还有一个目的是,本人也想写一系列博文来分享自己对mybatis的理解。然而。个人文笔太差,列出的这些别人的文章我也会细心研读,并希望在阅读这些文章的同时既加深我对mybatis的理解,又能探索出一种更加通...

qpzkobe的博客 387

基于Java+SSM框架的大学生成绩管理系统设计与实现

成绩管理系统是教育信息化中的关键组件,其核心在于通过数据库技术与Web框架实现教务流程数字化。Java+SSM(Spring+SpringMVC+MyBatis)作为主流企业级开发框架组合,通过Spring的IoC容器管理对象生命周期,MyBatis简化数据库操作,SpringMVC处理请求路由,构建出高内聚低耦合的系统架构。在高校场景中,该系统需要处理成绩批量导入、实时统计分析等高并发需求,采用MySQL事务控制与Redis缓存能有效提升性能。典型实现包含RBAC权限控制、Excel数据解析(Apache

weixin_33736832的博客 389

《Mybatis源码解析》一篇懂

1、MyBatis的架构设计以及实例分析 2、Mybatis初始化机制详解 3、Mybatis数据源与连接池 4、MyBatis事务管理机制 5、MyBatis缓存机制的设计与实现 6、MyBatis的一级缓存实现详解 及使用注意事项 7、MyBatis的二级缓存的设计原理 8、如何细粒度地控制你的MyBatis二级缓存(mybatis-enhanced-cache插件实现) 9、...

烟雨gps 183
上一篇: 使用Redis做MyBatis的二级缓存
下一篇: redis del命令支持正则删除(pattern)
hxpjava1
博客等级 码龄19年 1365粉丝 511原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值