作者在学习Redis整合时使用JDBC与Jedis,但是呢,现如今的环境下,Mybatis系列ORM框架是更受关注的方法,作者有一点点Mybatis基础,Mybatisplus几乎忘的差不多了,现对Redis整合Mybatis相关知识进行梳理,在进一步学习Mybatis系列知识后,再将具体代码进行补充测试。
结合 MyBatis 和 Redis 进行缓存可以通过 MyBatis 提供的缓存机制来实现。下面的例子将指导你如何使用 MyBatis-Redis-Cache 这个库进行整合。
添加依赖: 在你的 pom.xml 文件中添加 MyBatis 和 MyBatis-Redis-Cache 的依赖。
- <dependencies>
-
- <dependency>
- <groupId>org.mybatis.spring.bootgroupId>
- <artifactId>mybatis-spring-boot-starterartifactId>
- <version>2.1.4version>
- dependency>
-
- <dependency>
- <groupId>org.mybatis.cachesgroupId>
- <artifactId>mybatis-redisartifactId>
- <version>1.0.0version>
- dependency>
- dependencies>
配置 Redis: 在 application.properties 或 application.yml 文件中配置 Redis 的相关信息。
- # Redis 配置
- spring.redis.host=你的Redis服务器地址
- spring.redis.port=6379
配置 MyBatis: 在 MyBatis 的配置文件(例如 mybatis-config.xml)中开启缓存。
- <settings>
- <setting name="cacheEnabled" value="true"/>
- settings>
假设你有一个 UserMapper 接口和相应的 UserMapper.xml 文件:
在 UserMapper.xml 文件中开启缓存:
- <mapper namespace="com.example.mapper.UserMapper">
-
- <cache type="org.mybatis.caches.redis.RedisCache" />
-
- <select id="selectUserById" resultType="User">
- SELECT * FROM user WHERE id = #{id}
- select>
- mapper>
在服务层使用 UserMapper:
- @Service
- public class UserService {
- @Autowired
- private UserMapper userMapper;
-
- public User getUserById(int id) {
- // 首次调用将查询数据库
- // 之后的调用将直接从 Redis 缓存中获取结果
- return userMapper.selectUserById(id);
- }
- }
在 MyBatis 的 Mapper XML 文件中使用
标签和属性时,这是在告诉 MyBatis 使用默认的缓存实现。
初始化时的操作:
标签时,它会尝试实例化指定类型(type 属性)的缓存对象。com.example.cache.RedisCache)会被实例化,并且在需要缓存操作时被 MyBatis 调用。执行查询时的操作:
selectUserById)时,MyBatis 会首先检查缓存是否已经包含该查询的结果。因为在定义SQL语言之前,命名空间里还声明了 这种声明会让Mybatis做出对缓存的检查。关闭操作:
在使用 MyBatis 和 Redis 缓存时,如果你想要更多地控制 Redis 的详细操作,你可能需要自定义缓存处理逻辑。
其本质就是创建一个类,实现 MyBatis 的 Cache 接口。然后就把自定义类的名字换成之前的RedisCache就行,都是一样的配置。其自定义类代码如下所示:
- import org.apache.ibatis.cache.Cache;
- import redis.clients.jedis.Jedis;
-
- import java.util.concurrent.locks.ReadWriteLock;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
-
- public class MyRedisCache implements Cache {
-
- private final String id;
- private final Jedis redisClient = new Jedis("localhost", 6379);
-
- public MyRedisCache(String id) {
- this.id = id;
- }
-
- @Override
- public String getId() {
- return this.id;
- }
-
- @Override
- public void putObject(Object key, Object value) {
- redisClient.set(key.toString(), value.toString());
- }
-
- @Override
- public Object getObject(Object key) {
- return redisClient.get(key.toString());
- }
-
- @Override
- public Object removeObject(Object key) {
- return redisClient.del(key.toString());
- }
-
- @Override
- public void clear() {
- redisClient.flushDB();
- }
-
- @Override
- public int getSize() {
- return Integer.parseInt(redisClient.dbSize().toString());
- }
-
- @Override
- public ReadWriteLock getReadWriteLock() {
- return new ReentrantReadWriteLock();
- }
- }
在 MyBatis 的 Mapper XML 文件中使用自定义缓存类:
- <mapper namespace="com.example.mapper.UserMapper">
-
- <cache type="com.example.cache.MyRedisCache" />
-
- <select id="selectUserById" resultType="User">
- SELECT * FROM user WHERE id = #{id}
- select>
- mapper>
类定义中,读者可能会发现,咋多了这么多成员变量和方法啊?这其实是实现org.apache.ibatis.cache.Cache接口覆盖的成员和方法。
其中,有四个必须覆盖的方法:
String getId():返回缓存的标识符。每个缓存都必须有一个唯一的标识符。
void putObject(Object key, Object value):将查询结果存储在缓存中。
Object getObject(Object key):从缓存中获取由key指定的查询结果。
void clear():清除缓存中的所有项目。
三个可选覆盖的方法:
Object removeObject(Object key);int getSize();ReadWriteLock getReadWriteLock();
这些方法是可选的,意味着不一定非要覆盖它们。removeObject() 用于从缓存中移除对象,但并没有被 MyBatis 核心框架调用。同样,getSize() 和 getReadWriteLock() 也是可选的,并不是 MyBatis 核心部分的调用。这三个方法的实现依赖于你的实际缓存策略和需求。
为了更好地理解,作者查到16年某文章的cache源码。
- package org.apache.ibatis.cache;
-
- import java.util.concurrent.locks.ReadWriteLock;
-
-
- /**
- * 缓存接口
- * 给缓存供应商的SPI(Service Provider Interface)
- * 一个Cache的实例将为名称空间被创建
- * Cache接口的实现类必须有一个具有String类型参数的构造方法,用于接收Cache对象的id,作为其唯一标识
- *
- * mybatis将以namespace作为id调用这个构造函数创建对象
- *
- * @author Administrator
- *
- */
- public interface Cache {
-
- /**
- * 获取缓存对象的唯一标识
- * @return
- */
- String getId();
-
- /**
- * 保存key/value到缓存对象中
- * key可以是任何对象,但一般是CacheKey对象
- * value是查询结果,为List类型
- * @param key
- * @param value
- */
- void putObject(Object key, Object value);
-
- /**
- * 从缓存对象中获取key对应的value
- * @param key
- * @return
- */
- Object getObject(Object key);
-
- /**
- * 可选的方法,没有被核心框架调用,移除key对应的value
- * @param key
- * @return
- */
- Object removeObject(Object key);
-
- /**
- * 清空缓存
- */
- void clear();
-
- /**
- * 获取缓存对象中存储的键/值对的数量
- * 可选的方法,没有被框架核心调用
- */
- int getSize();
-
- /**
- * 获取读写锁
- * 可选的方法,从3.2.6起这个方法不再被框架核心调用
- * 任何需要的锁,都必须由缓存供应商提供
- *
- * @return A ReadWriteLock
- */
- ReadWriteLock getReadWriteLock();
-
- }
可以在自定义缓存类中添加其他方法。
但是,这些额外的方法不会被 MyBatis 自动调用,它们只能在Override覆盖的方法内部调用。比如你想添加一个方法来计算 Redis 缓存的大小,你可以添加一个 size() 方法到你的自定义缓存类,并在 putObject 方法中调用它来更新缓存大小的信息。比如:
- public class MyRedisCache implements Cache {
-
- // ...其他方法
-
- public int size() {
- // 实现计算 Redis 缓存大小的逻辑
- return 0;
- }
-
- @Override
- public void putObject(Object key, Object value) {
- // 在存储对象之前或之后更新缓存大小信息
- // ...
- int currentSize = size();
- // ...
- }
- }
注意,在MyBatis中:
getId(): MyBatis会自动调用这个方法来获取缓存的唯一标识符。putObject(Object key, Object value): MyBatis会在将查询结果存入缓存时调用这个方法。getObject(Object key): MyBatis会在从缓存中获取对象时调用这个方法。clear(): 在执行更新、插入或删除操作时,MyBatis会调用这个方法来清空命名空间下的缓存。
removeObject(Object key): 这个方法在MyBatis核心框架中并不会被自动调用,但是你可以在自定义缓存实现中使用它。getSize(): 同样,这个方法也不会被MyBatis核心框架自动调用,除非你自己调用它。getReadWriteLock(): 从3.2.6版本开始,这个方法不再被框架核心调用。任何需要的锁,都必须由缓存提供商自行实现和管理。
总的来说,getId(), putObject(), getObject(), 和 clear() 这四个方法是在MyBatis操作过程中会被自动调用的,其他方法(removeObject(), getSize(), getReadWriteLock()和其他非Cache接口的方法)则不会,需要根据实际情况自行调用或实现。
考虑以下 Mapper XML 文件:
- <mapper namespace="com.example.mapper.UserMapper">
- <cache type="com.example.cache.MyRedisCache" />
- <select id="selectUserById" resultType="User">
- SELECT * FROM user WHERE id = #{id}
- select>
- mapper>
当执行 selectUserById 查询时:
MyRedisCache 类的 getObject 方法来尝试从 Redis 缓存中获取结果。getObject 返回 null(缓存未命中),MyBatis 将执行 SQL 查询并将结果通过 putObject 方法存储到 Redis 缓存中。getObject 返回非 null(缓存命中),MyBatis 将直接返回缓存的结果。既可以通过藏在自动调用的函数(不过这应该不属于手动调用),也可以通过获取 SqlSessionFactory 的 Configuration,然后获取特定的 Cache 对象来调用这个方法。不过貌似这种创建实例对象的方案不是很流行,具体等作者多学一点知识再说。
下面是一个例子:
- SqlSessionFactory sqlSessionFactory = ... // 获取 SqlSessionFactory
- Configuration configuration = sqlSessionFactory.getConfiguration();
- Cache cache = configuration.getCache("com.example.cache.MyRedisCache"); // 使用你的 cache id
- int size = cache.getSize();
- System.out.println("Cache Size: " + size);
在这个例子中:
- 首先,获取了
SqlSessionFactory的实例。- 然后,从
SqlSessionFactory中获取了Configuration对象。- 接着,从
Configuration中通过 cache id 获取了Cache对象。- 最后,调用了
getSize()方法来获取缓存的大小。