Mybatis 如何自定义缓存?

MyBatis 通过实现org.apache.ibatis.cache.Cache 接口来自定义二级缓存,我们可以集成各种第三方缓存(如 Redis, Ehcache, Memcached 等)或实现自己特定的缓存逻辑。

以下是自定义 MyBatis 缓存的步骤和要点:

1. 实现 org.apache.ibatis.cache.Cache 接口

我们需要创建一个 Java 类来实现 org.apache.ibatis.cache.Cache 接口。这个接口定义了缓存需要实现的基本操作:

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 传入。
    public 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.xmlbuild.gradle)中。

示例:一个简单的基于 ConcurrentHashMap 的自定义缓存

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 属性指定你的自定义缓存类的全限定名

<!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> 子标签来传递:

<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="..."> 指定实现类。

### MyBatis的主要优点和特点 #### 优点 1. **灵活性**:MyBatis允许开发者编写自定义SQL语句,能够充分利用数据库的特定功能进行优化[^1]。这种灵活性使得在处理复杂查询时更加得心应手。 2. **易于学习和使用**:相比于其他ORM框架,MyBatis的学习曲线较平缓,开发者可以通过简单的XML配置或注解快速上手。 3. **良好的性能**:由于MyBatis允许手动编写SQL语句,因此可以针对具体场景进行性能优化,避免了全自动ORM框架可能带来的性能瓶颈。 4. **对现有数据库的兼容性好**:MyBatis支持多种数据库,并且可以通过手动配置适应不同的数据库特性[^1]。 5. **强大的动态SQL功能**:通过`trim`、`where`、`set`等标签,MyBatis能够根据条件动态生成SQL语句,极大地提高了开发效率[^3]。 6. **结果映射**:MyBatis提供了强大的结果映射功能,能够将复杂的查询结果轻松映射为Java对象[^4]。 #### 特点 1. **持久层框架**:MyBatis是一款专注于数据持久化的框架,属于ORM(对象关系映射)的一种实现形式[^4]。 2. **半自动化ORM**:与Hibernate等全自动ORM框架不同,MyBatis需要开发者手动编写SQL语句并定义映射规则,这虽然增加了工作量,但也带来了更高的灵活性。 3. **支持延迟加载和缓存**:MyBatis内置了缓存机制,包括一级缓存和二级缓存,同时支持延迟加载,能够有效提升查询性能[^4]。 4. **插件扩展性**:MyBatis提供了一个灵活的插件体系,开发者可以通过插件扩展框架的功能[^4]。 5. **兼容性与移植性**:尽管MyBatis对数据库的兼容性较好,但由于需要手动编写SQL语句,其移植性相对较差。 ```java // 示例:MyBatis中的动态SQL <select id="findActiveBlogWithTitleLike" parameterType="map" resultType="Blog"> SELECT * FROM BLOG WHERE state = 'ACTIVE' <if test="title != null"> AND title like #{title} </if> </select> ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值