MyBatis -- 整合Redis二级缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36311372/article/details/79090070

一。MyBatis一级二级缓存 

 

    级缓存:

    

    MyBatis一级缓存为SqlSession级别的缓存,默认开启,相同的SqlSession对象查询相同条件的结果时,如果存在一级缓存,那么只会访问数据库一次,一级缓存在sqlSession关闭后失效,调用cleanCache后会被清除,执行过增删改后缓存也会被清除。注意:一级缓存不能跨session

    

以一个根据Student Id查询Student为例:

    @Test
    public void query1() throws IOException{

            SqlSession session = getSession();

            // 获取映射接口实例
            StudentMapper mapper = session.getMapper(StudentMapper.class);
            Student stu1_1 = mapper.queryStudentById("1");
            Student stu1_2 = mapper.queryStudentById("1");
            System.out.println(stu1_1 == stu1_2);
    }

输出结果:

 

还可以看出SQL语句只执行了一次,这就是MyBatis的一级缓存。记住这个概念:

同一个SqlSession查询相同条件的结果时,存在一级缓存只会查询一次。第一次查询

获取到数据后会通过session设置到一级缓存中,第二次查询时通过session一级缓存

判断是否存在相同主键的数据,存在则直接返回引用,否则查询数据库。

 

二级缓存:

二级缓存为SqlSessionFactory级别的缓存,默认不开启,要开启的话走下面这个步骤:

1.首先要在核心配置文件中配置,这里要注意顺序,要写在properties声明后:

    MyBatis官方文档上的说明:

也就是说默认是打开的,直接用就OK了。不过这个开关只能控制二级缓存,一级缓存是不受影响的。

2.到需要使用二级缓存的namespace中定义:

 

 

属性详解:
eviction:回收策略,默认使用LRU算法。
也可以自己改,这里就改成了FIFO(队列),会把旧的数据清除掉,最先加入的数据最先
清除,不过这么做是不行的,如果最先加入的数据是热点数据,访问量大,清除掉了又要
重新去数据库中拉取,所以又有了LRU算法。

 

官方文档上的解释:

 

可用的收回策略有:

  • LRU – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱
    引用规则的对象。

默认的是 LRU。

 

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

 

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。

 

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。

 

测试方法:

 

 

 

 

  @Test

    public void cacheLevel2() throws IOException {

            // 获取工厂

            SqlSessionFactory factory = getFactory();

            

            // 从同一个工厂中获取session

            SqlSession session1 = factory.openSession();

            SqlSession session2 = factory.openSession();

            

            StudentMapper mapper1 = session1.getMapper(StudentMapper.class);

            StudentMapper mapper2 = session2.getMapper(StudentMapper.class);

            

            Student stu1 = mapper1.queryStudentById("1");

              session1.close();

            

            Student stu2 = mapper2.queryStudentById("1");
            

            System.out.println(stu1 == stu2);

    }

注意:如果不把session关掉,查询结果是不会设置到缓存中的,一个缓存一次只允许一个会话操作。因为考虑到如果两个会话同时操作一份缓存,会引起线程并发修改的问题,所以要关掉最先查询的那个session,查询结果才会被写到二级缓存中。 


运行结果:

 

 

 

二。集成Redis

 

    上面提到的FIFO   LRU之类的缓存策略默认都有实现类来支持他们,这些策略都是实现自

    Cache接口的,我们要通过MyBatis集成Redis实现自己的缓存策略,也是自定义一个实现类

    来实现。不多BB,先上代码。

 

 准备一个JavaBean:

实现Serializable的原因是我打算将Bean序列化成字节数组缓存到Redis中,取出来之后再反序列化成实体类 。既然要序列化和反序列化,还需要准备两个方法:

 

    import java.io.ByteArrayInputStream;

    import java.io.ByteArrayOutputStream;

    import java.io.IOException;

    import java.io.ObjectInputStream;

    import java.io.ObjectOutputStream;

    public class SerializableTools {

            /**         

            * 反序列化         

            *          

            * @param bt         

            * @return         

            * @throws IOException         

            * @throws Exception         

            */

            public static Object byteArrayToObj(byte[] bt) throws Exception {

                    ByteArrayInputStream bais = new ByteArrayInputStream(bt);

                    ObjectInputStream ois = new ObjectInputStream(bais);

                    return ois.readObject();

            }


            /**         

            * 对象序列化         

            *          

            * @param obj         

            * @return         

            * @throws IOException         

            */

            public static byte[] ObjToByteArray(Object obj) throws IOException {

                    ByteArrayOutputStream bos = new ByteArrayOutputStream();

                    ObjectOutputStream oos = new ObjectOutputStream(bos);

                    oos.writeObject(obj);

                    return bos.toByteArray();

            }

    }

 

OK,现在再定义个缓存策略实现类,实现Cache接口,并重写该类的一系列方法:

 

    import java.io.IOException;

    import java.util.concurrent.locks.ReadWriteLock;

    import java.util.concurrent.locks.ReentrantReadWriteLock;

    import org.apache.ibatis.cache.Cache;

    import redis.clients.jedis.Jedis;

    public class RedisCache implements Cache {

            

            // 初始化Jedis

            private Jedis jedis = new Jedis("127.0.0.1", 6379);

            

            /*         

            *  MyBatis会把映射文件的命名空间作为         

            *  唯一标识cacheId,标识这个缓存策略属于哪个namespace         

            *  这里定义好,并提供一个构造器,初始化这个cacheId即可         

            */

            private String cacheId;

            

            public RedisCache (String cacheId){

                    this.cacheId = cacheId;

            }


            /**         

            * 清空缓存         

            */

            @Override

            public void clear() {

                    // 但这方法不建议实现

            }


            @Override

            public String getId() {

                    return cacheId;

            }


            /**         

            * MyBatis会自动调用这个方法检测缓存         

            * 中是否存在该对象。既然是自己实现的缓存         

            * ,那么当然是到Redis中找了。         

            */

            @Override

            public Object getObject(Object arg0) {

                    // arg0 在这里是键

                    try {

                            byte [] bt = jedis.get(SerializableTools.ObjToByteArray(arg0));

                            if (bt == null) {        // 如果没有这个对象,直接返回null

                                    return null;

                            }

                            return SerializableTools.byteArrayToObj(bt);

                    } catch (Exception e) {

                            e.printStackTrace();

                    }

                    return null;

            }


            @Override

            public ReadWriteLock getReadWriteLock() {

                    return new ReentrantReadWriteLock();

            }


            @Override

            public int getSize() {

                    return Integer.parseInt(Long.toString(jedis.dbSize()));

            }


            /**         

            * MyBatis在读取数据时,会自动调用此方法         

            * 将数据设置到缓存中。这里就写入Redis         

            */

            @Override

            public void putObject(Object arg0, Object arg1) {

                    /*                 

                    *  arg0是key , arg1是值                 

                    *  MyBatis会把查询条件当做键,查询结果当做值。                 

                    */

                    try {

                            jedis.set(SerializableTools.ObjToByteArray(arg0), SerializableTools.ObjToByteArray(arg1));

                    } catch (IOException e) {

                            e.printStackTrace();

                    }

            }


            /**         

            * MyBatis缓存策略会自动检测内存的大小,由此         

            * 决定是否删除缓存中的某些数据         

            */

            @Override

            public Object removeObject(Object arg0) {

                    Object object = getObject(arg0);

                    try {

                            jedis.del(SerializableTools.ObjToByteArray(arg0));

                    } catch (IOException e) {

                            e.printStackTrace();

                    }

                    return object;

            }

    }

最后在配置文件中写上缓存实现类的全类名

 

 

 

以及在需要使用该缓存类的SQL语句上声明:

至此,自实现的二级缓存就可以用了。

 

 

 

展开阅读全文

没有更多推荐了,返回首页