MyBatis 二级缓存使用

2018/01/08 15:59
阅读数 1W

一级缓存和二级缓存的区别:

1、一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为同一个SqlSession,当Session flush或close之后,该Session中的所有Cache就将清空。

2、二级缓存:与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。

3、对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有select中的缓存将被clear。

如果要实现MyBatis的二级缓存,一般来说有如下两种方式:

1. 采用MyBatis内置的Cache机制。

2. 采用三方Cache框架, 比如EhCache, OSCache等等。

下面是基于MyBatis的内置的一级和二级缓存测试:

1、一级缓存配置

默认是开启的,如果不想用缓存,直接在select节点中增加useCache="false"和flushCache="true"属性即可。如:

<select id="getUserArticles" parameterType="int" resultMap="resultUserArticleList" useCache="false" flushCache="true">
        select user.id,user.userName,user.userAddress,article.id as aid,article.title,article.content from user,article where user.id=article.userid and user.id=#{id}
   </select>

flushCache:将其设置为true,无论语句什么时候被调用,都会导致缓存被清空。默认值:false。

useCache:将其设置为true,将会导致本条语句的结果被缓存。默认值:true。

测试代码如下:

注意:此时在select节点中缓存是开启的。

package com.jsoft.testmybatis.test1;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.jsoft.testmybatis.inter.IUserOperation;
import com.jsoft.testmybatis.models.Article;
import com.jsoft.testmybatis.models.User;

public class App {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("Configuration.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            IUserOperation userOperation = session.getMapper(IUserOperation.class);
            
            try {
                System.out.println("第一次查询开始");
                //List
                List<User> users = userOperation.selectUsers("%");
                for (User tempUser : users) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("第一次查询结束");
                
                System.out.println("第二次查询开始");
                //List
                List<User> users2 = userOperation.selectUsers("%");
                for (User tempUser : users2) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("第二次查询结束");
                
                System.out.println("加入数据开始开始");
                //Add
                User addUser = new User();
                addUser.setUserAddress("guangdong,guangzhou");
                addUser.setUserName("eason");
                addUser.setUserAge("80");
                int addRetCount = userOperation.addUser(addUser);
                session.commit();//必须提交事务,否则不会写入到数据库。如果session不commit,那么,数据就不会放入cache中。所以,只有commit后,才能取得。
                System.out.println("增加数据影响的行数:" + addRetCount);
                if (addUser.getId() > 0) {
                    System.out.println("增加数据成功,新增的id为:" + addUser.getId());
                }
                System.out.println("加入数据开始结束");
                
                System.out.println("第三次查询开始");
                //List
                List<User> users3 = userOperation.selectUsers("%");
                for (User tempUser : users3) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("第三次查询结束");
                
                //强制刷新缓存
                session.clearCache();
                
                System.out.println("第四次查询开始");
                //List
                List<User> users4 = userOperation.selectUsers("%");
                for (User tempUser : users4) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("第三次查询结束");
                
                
            } catch (Exception e) {
                // TODO: handle exception
                session.rollback();//有异常时回滚数据
                e.printStackTrace();
            }
            
        } finally {
            session.close();//close之后缓存清空
        }
    }
}

结果如下:

可以看出,缓存生效了。

下面测试select节点中配置缓存关闭的情况,结果如下:

可以看出缓存去除了,全部都是真实查询。

其实上面的测试示例还少了一个Session2的测试,不然效果不佳。

2、二级缓存测试:

开启二级缓存,在XML配置文件中添加Cache节点即可,参考配置如下:

<cache eviction="FIFO"  <!--回收策略为先进先出 -->
        flushInterval="60000" <!--自动刷新时间60s -->
        size="512" <!--最多缓存512个引用对象 -->
        readOnly="true"/> <!--只读 -->

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。可用的收回策略有, 默认的是 LRU:

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

测试代码如下:

package com.jsoft.testmybatis.test1;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.jsoft.testmybatis.inter.IUserOperation;
import com.jsoft.testmybatis.models.Article;
import com.jsoft.testmybatis.models.User;

public class App2 {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("Configuration.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        SqlSession session2 = sqlSessionFactory.openSession();
        try {
            IUserOperation userOperation = session.getMapper(IUserOperation.class);
            IUserOperation userOperation2 = session.getMapper(IUserOperation.class);
            try {
                System.out.println("Session1第一次查询开始");
                //List
                List<User> users = userOperation.selectUsers("%");
                for (User tempUser : users) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("Session1第一次查询结束");
                
                System.out.println("Session2第二次查询开始");
                //List
                List<User> users2 = userOperation2.selectUsers("%");
                for (User tempUser : users2) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("Session2第二次查询结束");
                
                System.out.println("Session1加入数据开始开始");
                //Add
                User addUser = new User();
                addUser.setUserAddress("guangdong,guangzhou");
                addUser.setUserName("eason");
                addUser.setUserAge("80");
                int addRetCount = userOperation.addUser(addUser);
                session.commit();//必须提交事务,否则不会写入到数据库。如果session不commit,那么,数据就不会放入cache中。所以,只有commit后,才能取得。
                System.out.println("增加数据影响的行数:" + addRetCount);
                if (addUser.getId() > 0) {
                    System.out.println("增加数据成功,新增的id为:" + addUser.getId());
                }
                System.out.println("Session1加入数据开始结束");
                
                System.out.println("Session1第三次查询开始");
                //List
                List<User> users3 = userOperation.selectUsers("%");
                for (User tempUser : users3) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("Session1第三次查询结束");
                
                //强制刷新缓存
                session.clearCache();
                
                System.out.println("Session1第四次查询开始");
                //List
                List<User> users4 = userOperation.selectUsers("%");
                for (User tempUser : users4) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("Session1第四次查询结束");
                

                System.out.println("Session2第五次查询开始");
                //List
                List<User> users5 = userOperation2.selectUsers("%");
                for (User tempUser : users5) {
                    System.out.println(tempUser.getUserAddress());
                    System.out.println(tempUser.getUserName());
                }
                System.out.println("Session2第五次查询结束");
            } catch (Exception e) {
                // TODO: handle exception
                session.rollback();//有异常时回滚数据
                session2.rollback();//有异常时回滚数据
                e.printStackTrace();
            }
            
        } finally {
            session.close();//close之后缓存清空
            session2.close();//close之后缓存清空
        }
    }
}

测试结果如下:

可以看出Cache不受Session的限制,且操作缓存的方法是一致的。

3、总结

1、映射语句文件中的所有select语句将会被缓存。

2、映射语句文件中的所有insert,update和delete语句会刷新缓存。

3、缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。

4、缓存会根据指定的时间间隔来刷新。

5、每个缓存可以存储 1024 个列表或对象的引用(不管查询出来的结果是什么) 。

6、缓存将作为“读/写”缓存,意味着获取的对象不是共享的且对调用者是安全的。不会有其它的调用者或线程潜在修改。

4、参考:

http://www.yihaomen.com/article/java/428.htm(文中结论主要转自此篇)

http://www.cnblogs.com/xdp-gacl/p/4270403.html(文中结论主要转自此篇)

http://blog.csdn.net/u012373815/article/details/47069223

http://blog.csdn.net/luanlouis/article/details/41390801

http://blog.csdn.net/luanlouis/article/details/41280959

http://blog.csdn.net/luanlouis/article/details/41408341

5、测试工程:https://github.com/easonjim/5_java_example/tree/master/mybatis/test12

下面是第三方缓存框架EhCache的配置使用:

说明:为什么要使用EhCache,因为它提供了很多内置缓存没有的强大功能,比如缓存的集群等。但是有一点,EhCache使用的日志框架是slf4j,需要而外配置使用slf4j,如果不想使用slf4j,也可以配置使用log4j。下面就是使用log4j的示例。

1、添加POM依赖:

<!-- ehcache -->
        <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!-- change slf4j for log4j -->
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

而对于使用log4j组件时,需要下载一个特殊的包slf4j-log4j12才能正常输出日志,参考:http://jiajun-kucoo.blog.163.com/blog/static/64148688201352791439772/

2、在resources中增加ehcache.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <diskStore path="D:/ehcache" />
    <!--  
    name:Cache的唯一标识  
    maxElementsInMemory:内存中最大缓存对象数  
    maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大  
    eternal:Element是否永久有效,一但设置了,timeout将不起作用  
    overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中  
    timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大  
    timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大   
    diskPersistent:是否缓存虚拟机重启期数据  
    diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒  
    diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区  
    memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
    备注: 持久化到硬盘的路径由虚拟机参数"java.io.tmpdir"决定.  例如, 在Windows中, 会在此路径下 C:\Documents and Settings\Jim\Local Settings\Temp,在Linux中, 通常会在:/tmp下,测试:System.out.println(System.getProperty("java.io.tmpdir"));   
    -->
    <defaultCache maxElementsInMemory="1000"
        maxElementsOnDisk="10000000" 
        eternal="false" 
        overflowToDisk="true" 
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="FIFO">
    </defaultCache>
    <!--  
        <cache name="test" overflowToDisk="true" eternal="false"  
        timeToIdleSeconds="300" timeToLiveSeconds="600" maxElementsInMemory="1000"  
        maxElementsOnDisk="10" diskPersistent="true" diskExpiryThreadIntervalSeconds="300"  
        diskSpoolBufferSizeMB="100" memoryStoreEvictionPolicy="LRU" />  
    -->
</ehcache>

详细配置参考:http://blog.csdn.net/etttttss/article/details/17141485

3、在user.xml中,也就是Mapper文件配置第三方缓存:

<!-- 以下两个<cache>标签二选一,第一个可以输出日志,第二个不输出日志 -->
    <cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
    <!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->

4、最后在log4j.properties文件中增加日志的切入点

log4j.logger.net.sf.ehcache=DEBUG

这句话的意思是在这个包net.sf.ehcache下的DEBUG级别的日志全部输出。

5、打印的日志如下:

6、参考:

http://www.yihaomen.com/article/java/428.htm

http://www.mybatis.org/ehcache-cache/dependencies.html

7、测试工程:https://github.com/easonjim/5_java_example/tree/master/mybatis/test13

自定义自己的缓存:

其实从上面的配置来看,<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />节点中的type指定的是具体的类,而这个类继承的是Cache接口,所以只要自己自定义继承这个接口实现自己特定类也能达到缓存效果。

基本上不用自己写这些自定义缓存,因为提供的第三方缓存框架来说已经很强大了。

1、参考:http://www.cnblogs.com/lzy1991/p/5335249.html

 

 

 

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部