本文将介绍怎样自定义缓存,我们除了使用 MyBatis 提供的默认缓存方式,你还可以通过实现你自己的缓存或其他第三方缓存方案创建适配器来完全覆盖缓存行为。
<cache type="com.hxstrive.mybatis.cache.demo4.MyCustomCache" />
上面示例展示了如何使用一个自定义的缓存实现。 type 属性指定的类必须实现 org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中很多复杂的接口之一。
Cache 源码如下:
package org.apache.ibatis.cache; import java.util.concurrent.locks.ReadWriteLock; public interface Cache { /** 获取缓存ID,如:com.hxstrive.mybatis.cache.demo4.UserMapper */ String getId(); /** 获取缓存大小 */ int getSize(); /** 将对象放入到缓存 */ void putObject(Object key, Object value); /** 从缓存获取对象 */ Object getObject(Object key); /** 从缓存移除一个对象 */ Object removeObject(Object key); /** 清空缓存 */ void clear(); /** 获取一个读写锁对象 */ ReadWriteLock getReadWriteLock(); }
如果要配置你自定义的缓存,可以通过 <cache> 标签的子标签 <property> 标签指定自定义缓存对象成员变量的值,相当于调用该变量的 setter 方法。比如:下面代码会在你的缓存实现中调用一个称为 “setCacheFile(String file)” 的方法,然后将指定的缓存文件位置放到 cacheFile 属性中。
<cache type="com.domain.something.MyCustomCache"> <property name="cacheFile" value="D:\tmp\cache\data.tmp"/> </cache>
当然,你可以使用所有 Java 简单类型作为 JavaBeans 的属性, MyBatis 会自动进行转换。
记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有在相同命名空间的语句(<select>、<insert>、<update> 和 <delete>)绑定的缓存是一样的。语句可以修改和缓存交互的方式,或在语句的基础上使用两种简单的属性(flushCache、useCache)来完全排除它们。 默认情况下,语句是这样配置的:
<!-- select 语句使用缓存 --> <select ... flushCache="false" useCache="true" /> <insert ... flushCache="true" /> <update ... flushCache="true" /> <delete ... flushCache="true" />
如果你想改变默认的行为,只能设置 flushCache 和 useCache 属性。比如,在一些情况下你也许想排除从缓存中查询特定语句结果,或者你也许想要一个 <select> 语句来刷新缓存。相似地,你也许有一些 <update> 语句执行是不需要刷新缓存。
(1)MyBatis 配置文件 mybatis-cfg.xml 内容如下:
<?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> <properties resource="database.properties"/> <settings> <!-- 全局映射器启用缓存 --> <setting name="cacheEnabled" value="true" /> </settings> <environments default="MySqlDatabase" > <environment id="MySqlDatabase" > <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/hxstrive/mybatis/cache/demo4/UserMapper.xml" /> </mappers> </configuration>
(2)定义 JavaBean UserBean.java,代码如下:
package com.hxstrive.mybatis.cache.demo4; import java.io.Serializable; public class UserBean implements Serializable { private Integer userId; private String name; private String sex; private Integer age; // 忽略 getter 和 setter 方法 }
(3)定义 Mapper 文件,代码如下:
a、UserMapper.java
package com.hxstrive.mybatis.cache.demo4; import org.apache.ibatis.annotations.Param; public interface UserMapper { UserBean getUserById(@Param("userId") int userId); }
b、UserMapper.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.hxstrive.mybatis.cache.demo4.UserMapper"> <!-- 开启二级缓存,使用自定义缓存 --> <cache type="com.hxstrive.mybatis.cache.demo4.MyCustomCache"> <property name="cacheFile" value="D:\tmp\cache\data.tmp" /> </cache> <!-- 映射结果 --> <resultMap id="RESULT_MAP" type="com.hxstrive.mybatis.cache.demo4.UserBean"> <id column="user_id" jdbcType="INTEGER" property="userId" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="sex" jdbcType="VARCHAR" property="sex" /> <result column="age" jdbcType="INTEGER" property="age" /> </resultMap> <!-- 查询所有用户信息 --> <select id="getUserById" resultMap="RESULT_MAP" useCache="true"> select `user_id`, `name`, `sex`, `age` from `user` where `user_id`=#{userId} </select> </mapper>
(4)自定义缓存,代码如下:
package com.hxstrive.mybatis.cache.demo4; import com.alibaba.fastjson.JSONObject; import org.apache.commons.io.FileUtils; import org.apache.ibatis.cache.Cache; import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 自定义 Cache */ public class MyCustomCache implements Cache { // 缓存ID,如:com.hxstrive.mybatis.cache.demo4.UserMapper private String id; // 读写锁 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 缓存对象Map private Map<Object,Object> cache = new ConcurrentHashMap<>(); // 缓存磁盘文件 private String cacheFile; /** 会被 <cache> 的 <property> 调用 */ public void setCacheFile(String cacheFile) { this.cacheFile = cacheFile; System.out.println("setCacheFile() cacheFile=" + cacheFile); // 每次创建该对象时,从磁盘加载历史缓存 try { String str = FileUtils.readFileToString(new File(cacheFile), "UTF-8"); Map<Object,Object> tmpMap = JSONObject.parseObject(str, Map.class); for(Map.Entry<Object,Object> entry : tmpMap.entrySet()) { cache.put(entry.getKey(), entry.getValue()); } } catch (Exception e) { System.err.println("加载缓存失败," + e.getMessage()); e.printStackTrace(); } } public MyCustomCache(String id) { this.id = id; System.out.println("MyCustomCache id=" + id); } @Override public String getId() { System.out.println("getId()"); return id; } @Override public int getSize() { System.out.println("getSize()"); return cache.size(); } @Override public void putObject(Object key, Object value) { System.out.println("putObject() key=" + key + ", value=" + value); cache.put(key, value); // 将数据写入磁盘 try { String cacheStr = JSONObject.toJSONString(cache); FileUtils.write(new File(cacheFile), cacheStr, "UTF-8"); } catch (Exception e) { System.err.println("缓存持久化失败," + e.getMessage()); e.printStackTrace(); } } @Override public Object getObject(Object key) { System.out.println("getObject() key=" + key); return cache.get(key); } @Override public Object removeObject(Object key) { Object value = cache.get(key); if(null == value) { return null; } else { cache.remove(key); return value; } } @Override public void clear() { cache.clear(); } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } }
(5)客户端代码如下:
package com.hxstrive.mybatis.cache.demo4; 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 java.io.InputStream; public class CacheDemo { public static void main(String[] args) throws Exception { String cfgName = "com/hxstrive/mybatis/cache/demo4/mybatis-cfg.xml"; InputStream input = Resources.getResourceAsStream(cfgName); SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlFactory = factoryBuilder.build(input); getUserById(sqlFactory, 1); getUserById(sqlFactory, 2); getUserById(sqlFactory, 3); getUserById(sqlFactory, 3); getUserById(sqlFactory, 2); getUserById(sqlFactory, 1); } private static void getUserById(SqlSessionFactory sqlFactory, int userId) { System.out.println("查询用户信息:"); SqlSession sqlSession = sqlFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserBean userBean = userMapper.getUserById(userId); System.out.println(userBean + " -- " + userBean.getName()); sqlSession.close(); } }