前幾天網友chanfish 給我拋出了一個問題,籠統地講就是如何能細粒度地控制MyBatis的二級緩存問題,醞釀了幾天,覺得可以寫個插件來實現這個這一功能。本文就是從問題入手,一步步分析現存的MyBatis的二級緩存的不足之處,探討一點可以改進的地方,並且對不足之處開發一個插件進行彌補。
本文如下組織結構:
- 一個關於MyBatis的二級緩存的實際問題
- 當前MyBatis二級緩存的工作機制
- mybatis-enhanced-cache插件的設計和工作原理
- mybatis-enhanced-cache 插件的使用實例
1.一個關於MyBatis的二級緩存的實際問題
2. 當前MyBatis二級緩存的工作機制:
3.mybatis-enhanced-cache插件的設計和工作原理
4. mybatis-enhanced-cache 插件的使用實例:
1. 下載 mybatis-enhanced-cache.rar壓縮包,解壓,將其內的mybatis-enhanced-cache-0.0.1-SNAPSHOT.jar添加到項目的classpath下;
2. 配置MyBatis配置文件如下:
- <plugins>
- <plugin interceptor="org.luanlouis.mybatis.plugin.cache.EnhancedCachingExecutor">
- <property name="dependency" value="dependencys.xml"/>
- <property name="cacheEnabled" value="true"/>
- </plugin>
- </plugins>
- <plugins>
- <plugin interceptor="org.luanlouis.mybatis.plugin.cache.EnhancedCachingExecutor">
- <property name="dependency" value="dependencys.xml"/>
- <property name="cacheEnabled" value="true"/>
- </plugin>
- </plugins>
其中,<property name="dependency"> 中的value屬性是 StatementId之間的依賴關係的配置文件路徑。
3. 配置StatementId之間的依賴關係
- <?xml version="1.0" encoding="UTF-8"?>
- <dependencies>
- <statements>
- <statement id="com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey">
- <observer id="com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments" />
- </statement>
- </statements>
- </dependencies>
- <?xml version="1.0" encoding="UTF-8"?>
- <dependencies>
- <statements>
- <statement id="com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey">
- <observer id="com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments" />
- </statement>
- </statements>
- </dependencies>
<statement>節點配置的是更新語句的statementId,其內的子節點<observer> 配置的是當更新語句執行後,應當清空緩存的查詢語句的StatementId。子節點<observer>可以有多個。
如上的配置,則說明,如果"com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey" 更新語句執行後,由 “com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments” 語句所產生的放置在Cache緩存中的數據都都會被清空。
4. 配置DepartmentsMapper.xml 和EmployeesMapper.xml
- <?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.louis.mybatis.dao.DepartmentsMapper" >
- <cache></cache>
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Department" >
- <id column="DEPARTMENT_ID" property="departmentId" jdbcType="DECIMAL" />
- <result column="DEPARTMENT_NAME" property="departmentName" jdbcType="VARCHAR" />
- <result column="MANAGER_ID" property="managerId" jdbcType="DECIMAL" />
- <result column="LOCATION_ID" property="locationId" jdbcType="DECIMAL" />
- </resultMap>
- <sql id="Base_Column_List" >
- DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID
- </sql>
- <update id="updateByPrimaryKey" parameterType="com.louis.mybatis.model.Department" >
- update HR.DEPARTMENTS
- set DEPARTMENT_NAME = #{departmentName,jdbcType=VARCHAR},
- MANAGER_ID = #{managerId,jdbcType=DECIMAL},
- LOCATION_ID = #{locationId,jdbcType=DECIMAL}
- where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}
- </update>
- <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
- select
- <include refid="Base_Column_List" />
- from HR.DEPARTMENTS
- where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}
- </select>
- </mapper>
- <?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.louis.mybatis.dao.DepartmentsMapper" >
- <cache></cache>
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Department" >
- <id column="DEPARTMENT_ID" property="departmentId" jdbcType="DECIMAL" />
- <result column="DEPARTMENT_NAME" property="departmentName" jdbcType="VARCHAR" />
- <result column="MANAGER_ID" property="managerId" jdbcType="DECIMAL" />
- <result column="LOCATION_ID" property="locationId" jdbcType="DECIMAL" />
- </resultMap>
- <sql id="Base_Column_List" >
- DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, LOCATION_ID
- </sql>
- <update id="updateByPrimaryKey" parameterType="com.louis.mybatis.model.Department" >
- update HR.DEPARTMENTS
- set DEPARTMENT_NAME = #{departmentName,jdbcType=VARCHAR},
- MANAGER_ID = #{managerId,jdbcType=DECIMAL},
- LOCATION_ID = #{locationId,jdbcType=DECIMAL}
- where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}
- </update>
- <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
- select
- <include refid="Base_Column_List" />
- from HR.DEPARTMENTS
- where DEPARTMENT_ID = #{departmentId,jdbcType=DECIMAL}
- </select>
- </mapper>
- <?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.louis.mybatis.dao.EmployeesMapper">
- <cache eviction="LRU" flushInterval="100000" size="10000"/>
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee">
- <id column="EMPLOYEE_ID" jdbcType="DECIMAL" property="employeeId" />
- <result column="FIRST_NAME" jdbcType="VARCHAR" property="firstName" />
- <result column="LAST_NAME" jdbcType="VARCHAR" property="lastName" />
- <result column="EMAIL" jdbcType="VARCHAR" property="email" />
- <result column="PHONE_NUMBER" jdbcType="VARCHAR" property="phoneNumber" />
- <result column="HIRE_DATE" jdbcType="DATE" property="hireDate" />
- <result column="JOB_ID" jdbcType="VARCHAR" property="jobId" />
- <result column="SALARY" jdbcType="DECIMAL" property="salary" />
- <result column="COMMISSION_PCT" jdbcType="DECIMAL" property="commissionPct" />
- <result column="MANAGER_ID" jdbcType="DECIMAL" property="managerId" />
- <result column="DEPARTMENT_ID" jdbcType="DECIMAL" property="departmentId" />
- </resultMap>
- <sql id="Base_Column_List">
- EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY,
- COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID
- </sql>
- <select id="selectWithDepartments" parameterType="java.lang.Integer" resultMap="BaseResultMap" useCache="true" >
- select
- *
- from HR.EMPLOYEES t left join HR.DEPARTMENTS S ON T.DEPARTMENT_ID = S.DEPARTMENT_ID
- where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
- </select>
- </mapper>
- <?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.louis.mybatis.dao.EmployeesMapper">
- <cache eviction="LRU" flushInterval="100000" size="10000"/>
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee">
- <id column="EMPLOYEE_ID" jdbcType="DECIMAL" property="employeeId" />
- <result column="FIRST_NAME" jdbcType="VARCHAR" property="firstName" />
- <result column="LAST_NAME" jdbcType="VARCHAR" property="lastName" />
- <result column="EMAIL" jdbcType="VARCHAR" property="email" />
- <result column="PHONE_NUMBER" jdbcType="VARCHAR" property="phoneNumber" />
- <result column="HIRE_DATE" jdbcType="DATE" property="hireDate" />
- <result column="JOB_ID" jdbcType="VARCHAR" property="jobId" />
- <result column="SALARY" jdbcType="DECIMAL" property="salary" />
- <result column="COMMISSION_PCT" jdbcType="DECIMAL" property="commissionPct" />
- <result column="MANAGER_ID" jdbcType="DECIMAL" property="managerId" />
- <result column="DEPARTMENT_ID" jdbcType="DECIMAL" property="departmentId" />
- </resultMap>
- <sql id="Base_Column_List">
- EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB_ID, SALARY,
- COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID
- </sql>
- <select id="selectWithDepartments" parameterType="java.lang.Integer" resultMap="BaseResultMap" useCache="true" >
- select
- *
- from HR.EMPLOYEES t left join HR.DEPARTMENTS S ON T.DEPARTMENT_ID = S.DEPARTMENT_ID
- where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
- </select>
- </mapper>
5. 測試代碼:
- package com.louis.mybatis.test;
- import java.io.InputStream;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import org.apache.ibatis.io.Resources;
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
- import org.apache.log4j.Logger;
- import com.louis.mybatis.model.Department;
- import com.louis.mybatis.model.Employee;
- /**
- * SqlSession 簡單查詢演示類
- * @author louluan
- */
- public class SelectDemo3 {
- private static final Logger loger = Logger.getLogger(SelectDemo3.class);
- public static void main(String[] args) throws Exception {
- InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
- SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
- SqlSessionFactory factory = builder.build(inputStream);
- SqlSession sqlSession = factory.openSession(true);
- SqlSession sqlSession2 = factory.openSession(true);
- //3.使用SqlSession查詢
- Map<String,Object> params = new HashMap<String,Object>();
- params.put("employeeId",10);
- //a.查詢工資低於10000的員工
- Date first = new Date();
- //第一次查詢
- List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 11);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 12);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 13);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- Department department = sqlSession.selectOne("com.louis.mybatis.dao.DepartmentsMapper.selectByPrimaryKey",10);
- department.setDepartmentName("updated");
- sqlSession2.update("com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey", department);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- }
- public static void checkCacheStatus(SqlSession sqlSession)
- {
- loger.info("------------Cache Status------------");
- Iterator<String> iter = sqlSession.getConfiguration().getCacheNames().iterator();
- while(iter.hasNext())
- {
- String it = iter.next();
- loger.info(it+":"+sqlSession.getConfiguration().getCache(it).getSize());
- }
- loger.info("------------------------------------");
- }
- }
- package com.louis.mybatis.test;
- import java.io.InputStream;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import org.apache.ibatis.io.Resources;
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
- import org.apache.log4j.Logger;
- import com.louis.mybatis.model.Department;
- import com.louis.mybatis.model.Employee;
- /**
- * SqlSession 簡單查詢演示類
- * @author louluan
- */
- public class SelectDemo3 {
- private static final Logger loger = Logger.getLogger(SelectDemo3.class);
- public static void main(String[] args) throws Exception {
- InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
- SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
- SqlSessionFactory factory = builder.build(inputStream);
- SqlSession sqlSession = factory.openSession(true);
- SqlSession sqlSession2 = factory.openSession(true);
- //3.使用SqlSession查詢
- Map<String,Object> params = new HashMap<String,Object>();
- params.put("employeeId",10);
- //a.查詢工資低於10000的員工
- Date first = new Date();
- //第一次查詢
- List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 11);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 12);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- params.put("employeeId", 13);
- result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectWithDepartments",params);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- Department department = sqlSession.selectOne("com.louis.mybatis.dao.DepartmentsMapper.selectByPrimaryKey",10);
- department.setDepartmentName("updated");
- sqlSession2.update("com.louis.mybatis.dao.DepartmentsMapper.updateByPrimaryKey", department);
- sqlSession.commit();
- checkCacheStatus(sqlSession);
- }
- public static void checkCacheStatus(SqlSession sqlSession)
- {
- loger.info("------------Cache Status------------");
- Iterator<String> iter = sqlSession.getConfiguration().getCacheNames().iterator();
- while(iter.hasNext())
- {
- String it = iter.next();
- loger.info(it+":"+sqlSession.getConfiguration().getCache(it).getSize());
- }
- loger.info("------------------------------------");
- }
- }
結果輸出:
結果分析:
從上述的結果可以看出,前四次執行了“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"緩存的清空。