`
yanyuan904
  • 浏览: 1573 次
文章分类
社区版块
存档分类
最新评论

mybatis二级缓存--redis实现

 
阅读更多

背景

  在使用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基于Redis实现二级缓存过程解析 Mybatis-plus是一款基于Java语言的持久层框架,旨在简化数据库交互操作。然而,在高并发、高性能的应用场景中,数据库的查询操作可能会成为性能瓶颈。为了解决这个问题...

    SpringMVC-Mybatis-Shiro-redis

    在实际开发过程中,开发者可以根据项目需求调整这四个组件的具体配置,例如优化Mybatis的SQL执行效率,调整Shiro的权限策略,或者利用Redis的高级特性如发布订阅模式来实现更复杂的功能。此外,通过持续集成和自动化...

    mybatis二级缓存扩展-与redis集成

    要将MyBatis的二级缓存与Redis集成,首先需要在MyBatis的配置文件中定义自定义的Cache实现类,这个类通常会基于Java的Jedis库来操作Redis。在实现类中,你需要覆盖`put`、`get`、`remove`等关键方法,以实现数据的...

    从0到1项目搭建-集成 Redis 配置MyBatis二级缓存

    基于 SpringBoot 从0搭建一个企业级开发项目,基于SpringBoot 的项目,并集成MyBatis-Plus、Redis、Druid、Logback ,并使用 Redis 配置 MyBatis 二级缓存。

    springMybatis+redis三级缓存框架

    "springMybatis+redis三级缓存框架"是一个高效且灵活的解决方案,它将MyBatis的二级缓存与Redis相结合,形成一个三级缓存体系,以优化数据读取速度并减轻数据库压力。 首先,MyBatis作为一款轻量级的持久层框架,其...

    mybatis+redis缓存配置

    本文重点介绍的是如何结合SpringMVC框架、MyBatis以及Redis实现一个高效的二级缓存机制。 #### 二、Redis作为二级缓存实现 Redis是一个开源的高性能键值存储系统,它具有内存存储、低延迟和丰富的数据结构等特点,...

    springboot-mybatis-mysql-redis demo.zip

    MyBatis作为轻量级的持久层框架,提供了灵活的数据访问能力;MySQL作为广泛使用的开源关系型数据库,提供了稳定的数据存储服务;而Redis作为内存数据结构存储系统,常用于缓存和实时数据操作。本文将详细解析如何在...

    分布式系统架构——使用Redis做MyBatis的二级缓存 - CSDN博客1

    要实现MyBatis与Redis的二级缓存集成,首先需要配置MyBatis的`mybatis-config.xml`文件,声明Redis作为二级缓存的实现。这通常包括设置Redis服务器的地址、端口、密码等信息。同时,还需要配置Redis客户端库,如...

    mybatis-redis-1.0.0-beta1.zip

    通过以上步骤,开发者可以轻松地在MyBatis项目中引入Redis作为二级缓存,提升系统的整体性能。需要注意的是,合理地设计缓存策略和粒度,避免因缓存不当导致的问题,如数据一致性问题和内存溢出。同时,监控Redis的...

    mybatis二级缓存

    标题中的“mybatis二级缓存”指的是MyBatis框架中的一个重要特性,它是MyBatis缓存机制的一部分,用于提升数据库查询效率。MyBatis一级缓存是SqlSession级别的,而二级缓存则是在整个Mapper配置范围内的全局缓存,...

    mybatis+redis实现二级缓存

    在本项目中,“mybatis+redis实现二级缓存”是一个巧妙结合了MyBatis持久层框架与Redis内存数据库来优化数据访问速度的实践案例。下面将详细介绍这个项目中的关键技术点以及实现步骤。 首先,MyBatis是一个轻量级的...

    mybatis plus使用redis作为二级缓存的方法

    MyBatis Plus 使用 Redis 作为二级缓存的方法 MyBatis Plus 是一个基于 MyBatis 的增强工具,提供了许多实用的功能,其中之一就是支持使用 Redis 作为二级缓存。本文将详细介绍如何使用 MyBatis Plus 将 Redis 作为...

    springboot-redis-mybatis:redis实现mybatis的二级缓存

    redis实现mybatis的二级缓存 springboot 2.0.1.RELEASE 关键点: 1.自己实现的二级缓存,必须要有一个带id的构造函数,否则会报错。 2.我们使用Spring封装的redisTemplate来操作Redis。 网上所有介绍redis做二级缓存...

    Redis用作二级缓存

    将Redis集成到Mybatis的二级缓存中,可以实现跨SqlSession的数据共享,即使在不同的会话或请求之间,也可以复用之前查询的结果。 配置Redis作为Mybatis的二级缓存,需要以下几个步骤: 1. 添加依赖:在项目的pom....

    springboot2.x整合redis做mybatis的二级缓存

    本篇文章将深入探讨如何在Spring Boot 2.x项目中整合Redis作为MyBatis的二级缓存,并实现自定义键、自定义过期时间和自定义序列化方式。 首先,让我们了解什么是二级缓存。在MyBatis中,一级缓存是基于SqlSession的...

    spring-boot + mybatis + redis 作为二级缓存.zip

    spring-boot + mybatis + redis 作为二级缓存spring-boot-mybatis 与 redisspring-boot + mybatis + redis 作为二级缓存配置文件为application.yml。

    SpringMVC-Mybatis-Shiro-redis-master 权限集成缓存中实例

    上面配置是去掉了 Session 的存储Key 的作用域,之前设置的.itboy.net ,是写到当前域名的 一级域名 下,这样就可以做到N 个 二级域名 下,三级、四级....下 Session 都是共享的。 &lt;!-- 用户信息记住我功能的...

    详解Spring boot使用Redis集群替换mybatis二级缓存

    Spring Boot 使用 Redis 集群替换 MyBatis 二级缓存 Spring Boot 作为当前流行的 Java Web 框架,提供了许多便捷的功能来快速开发 Web 应用程序。其中,缓存机制是 Spring Boot 中一个非常重要的组件,缓存机制...

    MyBatis缓存(一级缓存、二级缓存)

    二级缓存的实现基于MyBatis的插件体系,可以通过配置启用并指定实现类,如 EhCache 或 Redis。 启用二级缓存需要在MyBatis的配置文件中开启缓存支持,并在对应的Mapper接口或XML配置中声明启用二级缓存。每个Mapper...

Global site tag (gtag.js) - Google Analytics