MyBatis 通过实现org.apache.ibatis.cache.Cache
接口来自定义二级缓存,我们可以集成各种第三方缓存(如 Redis, Ehcache, Memcached 等)或实现自己特定的缓存逻辑。
以下是自定义 MyBatis 缓存的步骤和要点:
1. 实现 org.apache.ibatis.cache.Cache
接口
我们需要创建一个 Java 类来实现 org.apache.ibatis.cache.Cache
接口。这个接口定义了缓存需要实现的基本操作:
java
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
// 获取缓存的唯一 ID,通常是 Mapper 的 namespace
String getId();
// 将查询结果放入缓存
// key: CacheKey 对象,包含了 MappedStatement ID, 参数, SQL, 分页等信息
// value: 查询结果对象 (需要考虑序列化)
void putObject(Object key, Object value);
// 从缓存中获取对象
// key: CacheKey 对象
// 返回: 缓存的对象,如果不存在则返回 null
Object getObject(Object key);
// 从缓存中移除指定的 key (可能返回被移除的对象,也可能返回 null)
// 在执行更新操作影响缓存时可能被调用,但更常见的是 clear() 被调用
Object removeObject(Object key);
// 清空整个缓存
// 通常在执行 INSERT, UPDATE, DELETE 操作后被调用 (如果 flushCache=true)
void clear();
// 获取缓存中存储的条目数量 (可能只是一个估计值)
int getSize();
// 获取读写锁 (可选)
// 如果你的缓存实现内部不是线程安全的,或者需要更精细的并发控制,
// 可以返回一个 ReadWriteLock 实例。
// 如果缓存本身是线程安全的 (如 ConcurrentHashMap 或多数外部缓存客户端),
// 或者你接受一定的并发风险以换取性能,可以返回 null。
// MyBatis 默认会使用这个锁来保证 put/get/remove/clear 操作的原子性。
ReadWriteLock getReadWriteLock();
}
2. 实现自定义缓存类
根据选择的缓存技术(或自定义逻辑)来实现上述接口的所有方法。关键点包括:
-
构造函数: 自定义缓存类必须 有一个接收
String id
作为参数的公共构造函数。MyBatis 会通过反射调用这个构造函数来实例化你的缓存类,并将 Mapper 的namespace
作为id
传入。javapublic class MyCustomCache implements Cache { private final String id; // ... 其他成员变量,如 Redis 连接池,Ehcache 实例等 public MyCustomCache(String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; // 初始化你的缓存客户端或存储结构 System.out.println("Initializing custom cache for namespace: " + id); } @Override public String getId() { return this.id; } // ... 实现其他方法 ... }
-
序列化: 由于二级缓存可能跨
SqlSession
共享,甚至可能存储在 JVM 外部(如 Redis),放入缓存的对象 (value
) 通常需要序列化。你需要确保你的实体类实现了java.io.Serializable
接口,并在putObject
中进行序列化,在getObject
中进行反序列化。CacheKey
对象 (key
) 本身是可序列化的。 -
线程安全:
Cache
实例会被多个线程(来自不同的SqlSession
)并发访问。你的实现必须是线程安全的。可以使用线程安全的数据结构(如ConcurrentHashMap
),或者利用底层缓存系统(如 Redis 客户端库)提供的线程安全操作,或者通过getReadWriteLock()
返回一个读写锁让 MyBatis 来管理并发。 -
依赖管理: 如果你使用第三方缓存库(如 Jedis, Lettuce, Ehcache),需要将相应的依赖添加到你的项目构建文件(
pom.xml
或build.gradle
)中。
示例:一个简单的基于 ConcurrentHashMap 的自定义缓存
java
import org.apache.ibatis.cache.Cache;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SimpleMapCache implements Cache {
private final String id;
private final ConcurrentHashMap<Object, Object> cache = new ConcurrentHashMap<>();
// 使用 ReentrantReadWriteLock 来演示锁的使用
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public SimpleMapCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
System.out.println("Initializing SimpleMapCache for namespace: " + id);
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
// 假设 value 已经是可直接存储的 (或已序列化)
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int getSize() {
return cache.size();
}
@Override
public ReadWriteLock getReadWriteLock() {
// 返回锁,让 MyBatis 在访问缓存时进行同步
return this.readWriteLock;
// 如果 cache 本身的操作是完全原子且线程安全的,理论上可以返回 null,
// 但返回锁是更标准的做法,确保 MyBatis 的缓存装饰器能正确工作。
// return null;
}
}
3. 配置 MyBatis 使用自定义缓存
在需要使用自定义缓存的 Mapper XML 文件中,使用 <cache>
标签,并通过 type
属性指定你的自定义缓存类的全限定名:
xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 配置使用自定义缓存 -->
<cache type="com.example.cache.SimpleMapCache"/>
<!-- 或者如果是 Redis 缓存 -->
<!-- <cache type="com.example.cache.MyRedisCache"/> -->
<select id="selectUserById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 其他 CRUD 操作 -->
</mapper>
4. (可选) 传递自定义属性
如果自定义缓存需要一些配置参数(比如 Redis 的主机、端口、密码等),你可以在 <cache>
标签内部使用 <property>
子标签来传递:
xml
<cache type="com.example.cache.MyRedisCache">
<property name="redis.host" value="127.0.0.1"/>
<property name="redis.port" value="6379"/>
<property name="redis.password" value="your_password"/>
<property name="cache.eviction.time.seconds" value="3600"/>
</cache>
如何让自定义缓存类接收这些属性?
标准的 MyBatis Cache
接口本身没有直接定义接收这些属性的方法。常见的处理方式有:
- 在构造函数或初始化块中读取: 自定义缓存类可以在其构造函数或静态初始化块中读取外部配置文件(如
.properties
文件)、系统属性或环境变量来获取配置。<property>
标签在这种情况下更像是一种注释或标记。 - 使用第三方集成库: 许多 MyBatis 的第三方缓存集成库(如
mybatis-redis
,mybatis-ehcache
)提供了自己的Cache
实现类或适配器。这些适配器通常会实现特定的方法(可能不是标准的setProperties
)或者在它们的构造函数中处理<property>
标签传入的属性。如果你使用这些库,请查阅它们的文档。 - 自定义处理 (较少见): 理论上可以扩展 MyBatis 的配置解析过程来注入这些属性,但这比较复杂,通常不推荐。
总结:
自定义 MyBatis 缓存的核心是实现 Cache
接口,并在 Mapper XML 中通过 <cache type="...">
指定实现类。