Redis与Mybatis
作者在学习Redis整合时使用JDBC与Jedis,但是呢,现如今的环境下,Mybatis系列ORM框架是更受关注的方法,作者有一点点Mybatis基础,Mybatisplus几乎忘的差不多了,现对Redis整合Mybatis相关知识进行梳理,在进一步学习Mybatis系列知识后,再将具体代码进行补充测试。
结合 MyBatis 和 Redis 进行缓存可以通过 MyBatis 提供的缓存机制来实现。下面的例子将指导你如何使用 MyBatis-Redis-Cache 这个库进行整合。
基本步骤
添加依赖: 在你的 pom.xml
文件中添加 MyBatis 和 MyBatis-Redis-Cache 的依赖。
<dependencies>
<!-- 添加MyBatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- 添加MyBatis-Redis-Cache依赖 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0</version>
</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">
<!-- 开启这个 namespace 的缓存 -->
<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 文件中使用
<cache type="com.example.cache.RedisCache" />
标签和属性时,这是在告诉 MyBatis 使用默认的缓存实现。
机制
-
初始化时的操作:
- 当 MyBatis 初始化时,它会读取 Mapper XML 文件,并根据 XML 文件内容创建相应的 Mapper 对象。
- 当 MyBatis 遇到
<cache>
标签时,它会尝试实例化指定类型(type
属性)的缓存对象。 - 指定的缓存类(在本例中是
com.example.cache.RedisCache
)会被实例化,并且在需要缓存操作时被 MyBatis 调用。
-
执行查询时的操作:
- 当执行查询(如
selectUserById
)时,MyBatis 会首先检查缓存是否已经包含该查询的结果。因为在定义SQL语言之前,命名空间里还声明了<cache type="com.example.cache.RedisCache" />
这种声明会让Mybatis做出对缓存的检查。 - 如果缓存中存在结果,MyBatis 就直接从缓存中返回结果,不再执行实际的 SQL 查询。
- 如果缓存中不存在结果,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>
Cache接口实现
类定义中,读者可能会发现,咋多了这么多成员变量和方法啊?这其实是实现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 核心部分的调用。这三个方法的实现依赖于你的实际缓存策略和需求。
Cache接口源码
为了更好地理解,作者查到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
查询时:
- MyBatis 会调用
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()
方法来获取缓存的大小。