mybatis二级缓存实现--protostuff序列化
来源:互联网 发布:软件项目经理面试 编辑:程序博客网 时间:2024/06/05 22:38
背景
在使用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
- mybatis二级缓存实现--protostuff序列化
- 使用Redis Cluster作为MyBatis的二级缓存并使用protostuff序列化数据
- Java利用protostuff实现高效序列化
- Protostuff序列化
- Protostuff序列化
- Protostuff序列化
- Protostuff序列化
- Protostuff工具实现ProtoBuf序列化使用详解
- 高性能序列化protostuff
- protostuff 序列化简单使用
- redis实现mybatis二级缓存
- mybatis redis 实现二级缓存
- Protostuff序列化和反序列化
- protostuff序列化/反序列化
- protostuff 序列化 list 不一致问题
- Protostuff自定义序列化(Delegate)解析
- Protostuff序列化时的注意事项
- 比较protoStuff和fastjson以及实现Serializable接口序列化的效率问题
- UIWebView 页面处理
- multiMap遍历方法
- win8,anaconda3 python3.6,安装tensorflow,亲测有效
- Sublime Text 安装Vue 语法高亮插件Vue Syntax Hightlight
- Unity3D基础篇----Shader学习笔记系列
- mybatis二级缓存实现--protostuff序列化
- Ubuntu下搭建和卸载svn服务器
- CountDownTimer
- javascript链接mysql数据库实例
- springmvc自定义注解拦截器方式实现注解功能拦截器的配置
- linux下recv 、send阻塞、非阻塞区别和用法
- Ajax之XMLHttpRequest对象(技术分析篇)
- 四类No(not only)SQL数据库的比较
- win8/8.1改win7系统全部教程之BIOS(1)