背景
在使用mybatis框架自带的二级缓存实现时有个问题就是: 部署多个实例会带来缓存不一致的情况,因为它是使用本地内存。于是有的选择不使用mybatis的二级缓存,干脆自己来写缓存和读缓存,一种普遍的做法就是先从redis中读取,没有就读库,然后再回写缓存供下次使用。这样会有两个问题, 第一 作为开发人员重点关注的应该是数据库,现在还要花费精力来关心缓存 ;第二 数据可能清除的不干净,比如有一条数据 A ,有单独存放他的一条缓存记录,也有存放了一个集合的,集合里面也包括了A记录,在更新A的时候须要清除A相关的数据,这样须要清除A的单条记录还要清除包括了A记录的集合,更可怕的是有时候我们并不知道A还在什么地方给缓存了。
解决方法
第一种:类似于spring事务的方式(切面)来对待缓存,在调用读方法的时候执行读缓存的切面,如果有数据就直接返回,没有则进一步读库,这里不做介绍,spring4 及之后版本已经提供了相关实现(@Cache注解)。第二种:自己实现mybatis的Cache接口,作为二级缓存实现 ,这也是本文介绍的。
具体实现
直接上代码
MybatisRedisCache:
public class MybatisRedisCache implements Cache{
private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
/** The ReadWriteLock. */
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Cluster jimClient;
private String id;
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}
public String getId() {
return this.id;
}
public void putObject(Object key, Object value) {
if(value == null){
return;
}
DataEntity data = new DataEntity();
data.setObject(value);
jimClient.hSet(id.getBytes(),key.toString().getBytes(), SerializeUtil.serialize(data));
}
public Object getObject(Object key) {
byte[] bytes = jimClient.hGet(id.getBytes(),key.toString().getBytes());
if(bytes == null || bytes.length == 0){
return null;
}
DataEntity data = SerializeUtil.deserialize(bytes,DataEntity.class);
return data.getObject();
}
public Object removeObject(Object key) {
return jimClient.hDel( id.getBytes(),key.toString().getBytes());
}
/**
* 更新的时候会调用
*/
public void clear() {
jimClient.del(id.getBytes());
}
public int getSize() {
return jimClient.hGetAll(id.getBytes()).size();
}
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
public static void setJimClient(Cluster jimClient){
MybatisRedisCache.jimClient = jimClient;
}
}
MybatisRedisCache 实现了mybatis的Cache接口。mybatis在使用Cache的实现时,每个mapper都会有一个Cache实例。其中id就是mapper.xml文件中的namespace,在实例化时mybatis会自动传入。mybatis划分的粒度就是namespace级别的,就是说每个mapper都有自己的缓存空间,在更新数据(增删改)操作是会清空当前namespace的缓存(调用clear()方法),而不是所有数据的缓存。看网上有些实现 clear方法直接就是清空了所有数据,这样命中率还是很低的。namespace A的更新操作导致namespace B的数据被清空显然是不合理的。
这种实现本质就是每个mapper都有一个名为namespace的HashMap(在redis中以Map来存放数据)。jimClient 是京东对redis的包装实现,兼容redis。jimClient 由springIOC管理,因为别的地方也有使用,而MybatisRedisCache并不由springIOC管理,这样问题来了,一个不受IOC管理的对象如何注入受IOC容器管理的对象。这里把jimClient 定义为static的成员变量,然后通过静态注入的方式来注入,下面的RedisCacheTransfer类主要就是赋值操作。
SerializeUtil类
public class SerializeUtil {
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();
private static Objenesis objenesis = new ObjenesisStd(true);
private SerializeUtil() {
}
@SuppressWarnings("unchecked")
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
if (schema != null) {
cachedSchema.put(cls, schema);
}
}
return schema;
}
/**
* 序列化(对象 -> 字节数组)
*/
@SuppressWarnings("unchecked")
public static <T> byte[] serialize(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
/**
* 反序列化(字节数组 -> 对象)
*/
public static <T> T deserialize(byte[] data, Class<T> cls) {
try {
T message = (T) objenesis.newInstance(cls);
Schema<T> schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
SerializeUtil 负责序列化,它基于protostuff 实现。protostuff包装了protobuf(google提供的序列化实现),而直接使用protobuf需要定义IDL文件,然后生成的bean类都好几千行,使用起来还是很不习惯的。protostuff可以序列化我们熟悉的bean对象。
关于序列化的选择最直接的就是java内置序列化,但是它有很多问题,而现在的序列化手段还是很多的,所以尝试点新的东西,比如现在rpc框架基本上不会使用java内置序列化。protostuff序列化的时候不会保存当前对象的类信息,所以反序列化的时候须要传递class信息。而我们放在缓存中的数据可是任何类型的,因此不能直接序列化我们要存放的对象,getObject()方法可是根据key直接获取对象的。DataEntity 的结构很简单,现在只有一个Object的属性(后期可以根据需求添加相关附加属性),它就是为了包装我们要缓存的数据而生的。其实protostuff虽然不保存当然序列化对象的类信息,但是会保存内部属性的类信息,所以DataEntity 里面的object就可以存放数据了,反序列化时object可以正常的被反序列化出来真实的类型。
RedisCacheTransfer类
public class RedisCacheTransfer {
@Autowired
public void setJimClient(Cluster jimClient) {
MybatisRedisCache.setJimClient(jimClient);
}
}
RedisCacheTransfer没啥好说的,为静态注入而生的。
DataEntity 类
public class DataEntity {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
DataEntity 作为数据的载体而生,适应protostuff的序列化方式。
实际使用时和mybatis的二级缓存使用一样,只需要将mapper.xml文件中的cache标签配置为MybatisRedisCache。有些不足的地方,还请指正,大家一起相互学习。整个实现算是一个大杂烩,把自己看到的好多东西揉在了一起。
参考资料:
https://github.com/mybatis/redis-cache
http://blog.csdn.net/xiadi934/article/details/50786293
<script type="text/javascript">
$(function () {
$('pre.prettyprint code').each(function () {
var lines = $(this).text().split('\n').length;
var $numbering = $('<ul/>').addClass('pre-numbering').hide();
$(this).addClass('has-numbering').parent().append($numbering);
for (i = 1; i <= lines; i++) {
$numbering.append($('<li/>').text(i));
};
$numbering.fadeIn(1700);
});
});
</script>
分享到:
相关推荐
Mybatis-plus基于Redis实现二级缓存过程解析 Mybatis-plus是一款基于Java语言的持久层框架,旨在简化数据库交互操作。然而,在高并发、高性能的应用场景中,数据库的查询操作可能会成为性能瓶颈。为了解决这个问题...
在实际开发过程中,开发者可以根据项目需求调整这四个组件的具体配置,例如优化Mybatis的SQL执行效率,调整Shiro的权限策略,或者利用Redis的高级特性如发布订阅模式来实现更复杂的功能。此外,通过持续集成和自动化...
要将MyBatis的二级缓存与Redis集成,首先需要在MyBatis的配置文件中定义自定义的Cache实现类,这个类通常会基于Java的Jedis库来操作Redis。在实现类中,你需要覆盖`put`、`get`、`remove`等关键方法,以实现数据的...
基于 SpringBoot 从0搭建一个企业级开发项目,基于SpringBoot 的项目,并集成MyBatis-Plus、Redis、Druid、Logback ,并使用 Redis 配置 MyBatis 二级缓存。
"springMybatis+redis三级缓存框架"是一个高效且灵活的解决方案,它将MyBatis的二级缓存与Redis相结合,形成一个三级缓存体系,以优化数据读取速度并减轻数据库压力。 首先,MyBatis作为一款轻量级的持久层框架,其...
本文重点介绍的是如何结合SpringMVC框架、MyBatis以及Redis实现一个高效的二级缓存机制。 #### 二、Redis作为二级缓存实现 Redis是一个开源的高性能键值存储系统,它具有内存存储、低延迟和丰富的数据结构等特点,...
MyBatis作为轻量级的持久层框架,提供了灵活的数据访问能力;MySQL作为广泛使用的开源关系型数据库,提供了稳定的数据存储服务;而Redis作为内存数据结构存储系统,常用于缓存和实时数据操作。本文将详细解析如何在...
要实现MyBatis与Redis的二级缓存集成,首先需要配置MyBatis的`mybatis-config.xml`文件,声明Redis作为二级缓存的实现。这通常包括设置Redis服务器的地址、端口、密码等信息。同时,还需要配置Redis客户端库,如...
通过以上步骤,开发者可以轻松地在MyBatis项目中引入Redis作为二级缓存,提升系统的整体性能。需要注意的是,合理地设计缓存策略和粒度,避免因缓存不当导致的问题,如数据一致性问题和内存溢出。同时,监控Redis的...
标题中的“mybatis二级缓存”指的是MyBatis框架中的一个重要特性,它是MyBatis缓存机制的一部分,用于提升数据库查询效率。MyBatis一级缓存是SqlSession级别的,而二级缓存则是在整个Mapper配置范围内的全局缓存,...
在本项目中,“mybatis+redis实现二级缓存”是一个巧妙结合了MyBatis持久层框架与Redis内存数据库来优化数据访问速度的实践案例。下面将详细介绍这个项目中的关键技术点以及实现步骤。 首先,MyBatis是一个轻量级的...
MyBatis Plus 使用 Redis 作为二级缓存的方法 MyBatis Plus 是一个基于 MyBatis 的增强工具,提供了许多实用的功能,其中之一就是支持使用 Redis 作为二级缓存。本文将详细介绍如何使用 MyBatis Plus 将 Redis 作为...
redis实现mybatis的二级缓存 springboot 2.0.1.RELEASE 关键点: 1.自己实现的二级缓存,必须要有一个带id的构造函数,否则会报错。 2.我们使用Spring封装的redisTemplate来操作Redis。 网上所有介绍redis做二级缓存...
将Redis集成到Mybatis的二级缓存中,可以实现跨SqlSession的数据共享,即使在不同的会话或请求之间,也可以复用之前查询的结果。 配置Redis作为Mybatis的二级缓存,需要以下几个步骤: 1. 添加依赖:在项目的pom....
本篇文章将深入探讨如何在Spring Boot 2.x项目中整合Redis作为MyBatis的二级缓存,并实现自定义键、自定义过期时间和自定义序列化方式。 首先,让我们了解什么是二级缓存。在MyBatis中,一级缓存是基于SqlSession的...
spring-boot + mybatis + redis 作为二级缓存spring-boot-mybatis 与 redisspring-boot + mybatis + redis 作为二级缓存配置文件为application.yml。
上面配置是去掉了 Session 的存储Key 的作用域,之前设置的.itboy.net ,是写到当前域名的 一级域名 下,这样就可以做到N 个 二级域名 下,三级、四级....下 Session 都是共享的。 <!-- 用户信息记住我功能的...
Spring Boot 使用 Redis 集群替换 MyBatis 二级缓存 Spring Boot 作为当前流行的 Java Web 框架,提供了许多便捷的功能来快速开发 Web 应用程序。其中,缓存机制是 Spring Boot 中一个非常重要的组件,缓存机制...
二级缓存的实现基于MyBatis的插件体系,可以通过配置启用并指定实现类,如 EhCache 或 Redis。 启用二级缓存需要在MyBatis的配置文件中开启缓存支持,并在对应的Mapper接口或XML配置中声明启用二级缓存。每个Mapper...