第七节:mybatis缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。

  1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

  2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

1,一级缓存


一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当Session flush或close 后,该Session中的所有Cache将被清空。 本地缓存不能被关闭, 但可以调用clearCache() 来清空本地缓存, 或者改变缓存的作用域。在mybatis3.1之后, 可以配置本地缓存的作用域,在mybatis.xml 中配置 。同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中,key:hashCode+查询的SqlId+编写的sql查询语句+参数

缓存使用方式:

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);

        Person person1 = mapper.getPerson(1);
        System.out.println(person1);

        Person person2 = mapper.getPerson(1);
        System.out.println(person2);

        System.out.println(person1 == person2);
    }
    finally
    {
        sqlSession.close();
    }
}

一级缓存失效的四种情况 (未 close SqlSession情况下)

1、不同的SqlSession对应不同的一级缓存

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession01 = sqlSessionFactory.openSession();
    SqlSession sqlSession02 = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper = sqlSession01.getMapper(PersonMapper.class);
        Person person1 = mapper.getPerson(1);
        System.out.println(person1);

        mapper = sqlSession02.getMapper(PersonMapper.class);
        Person person2 = mapper.getPerson(1);
        System.out.println(person2);

        System.out.println(person1 == person2);//false
    }
    finally
    {
        sqlSession01.close();
        sqlSession02.close();
    }
}

2、同一个SqlSession但是查询条件不同

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);

        Person person1 = mapper.getPerson(1);//查询id为1
        System.out.println(person1);

        Person person2 = mapper.getPerson(2);//查询id为2
        System.out.println(person2);

        System.out.println(person1 == person2);//false
    }
    finally
    {
        sqlSession.close();
    }
}

3、同一个SqlSession两次查询期间执行了任何一次增删改操作

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);

        Person person1 = mapper.getPerson(1);
        System.out.println(person1);

        mapper.deletePerson(2);//删除一个数据

        Person person2 = mapper.getPerson(1);
        System.out.println(person2);

        System.out.println(person1 == person2);//false
    }
    finally
    {
        sqlSession.commit();
        sqlSession.close();
    }
}

4、同一个SqlSession两次查询期间手动清空了缓存

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);

        Person person1 = mapper.getPerson(1);
        System.out.println(person1);

        sqlSession.clearCache();//手动清除了缓存

        Person person2 = mapper.getPerson(1);
        System.out.println(person2);

        System.out.println(person1 == person2);//false
    }
    finally
    {
        sqlSession.close();
    }
}

2、二级缓存


二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。

工作机制:

1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容;

不同namespace查出的数据会放在自己对应的缓存中(map),查出的数据都会被默认先放在一级缓存中。只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。

使用方式

1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
2)、去mapper.xml中配置使用二级缓存: 只有在哪个mapper下面配置下面的,才会用到二级缓存,否则即使开启二级全局缓存,二级缓存也不生效
    <cache></cache>

cache标签可以配置的属性:

eviction:缓存的回收策略:
    LRU – 最近最少使用的:移除最长时间不被使用的对象。
    FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    默认的是 LRU。
flushInterval:缓存刷新间隔
    缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
    true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
             mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
    false:非只读:mybatis觉得获取的数据可能会被修改。
            mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名: <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
        实现Cache接口即可;
blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。

3)、POJO需要实现序列化接口

二级缓存代码示例

1)<setting name="cacheEnabled" value="true"/>
2)public class Person implements Serializable{...}
3)mapper映射文件加入<cache></cache>

为了能观察日志情况,我们简单配置一下日志打印:

<setting name="logImpl" value="STDOUT_LOGGING" />

下面是测试代码

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper1 = sqlSession1.getMapper(PersonMapper.class);
        Person person1 = mapper1.getPerson(1);
        System.out.println(person1);

        sqlSession1.close();

        PersonMapper mapper2 = sqlSession2.getMapper(PersonMapper.class);
        Person person2 = mapper2.getPerson(1);
        System.out.println(person2);

    }
    finally
    {
        sqlSession2.close();
    }
}

解释:sqlSession1查询结果之后,关闭sqlSession1,会将结果写入二级缓存,然后sqlSession2查询会从二级缓存中查询,不从数据查询数据了。下面日志可以证明:

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Cache Hit Ratio [com.yefengyu.mybatis.mapper.PersonMapper]: 0.0
Opening JDBC Connection
Sun Jun 16 17:16:22 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Created connection 327177752.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
==>  Preparing: select * from person where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, first_name, last_name, age, email, address
<==        Row: 1, tom, Carine, 25, null, beijing
<==      Total: 1
Person{id=1, firstName='tom', lastName='Carine', age=25, email='null', address='beijing'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
Returned connection 327177752 to pool.
Cache Hit Ratio [com.yefengyu.mybatis.mapper.PersonMapper]: 0.5
Person{id=1, firstName='tom', lastName='Carine', age=25, email='null', address='beijing'}

注意:只有一级缓存关闭的情况下二级缓存才会生效,下面演示中一级缓存没有关闭,二级缓存没有起作用,注意sqlSession1.close()的位置

public static void main(String[] args)
    throws IOException
{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    try
    {
        PersonMapper mapper1 = sqlSession1.getMapper(PersonMapper.class);
        Person person1 = mapper1.getPerson(1);
        System.out.println(person1);
        
        PersonMapper mapper2 = sqlSession2.getMapper(PersonMapper.class);
        Person person2 = mapper2.getPerson(1);
        System.out.println(person2);

    }
    finally
    {
        sqlSession1.close();
        sqlSession2.close();
    }
}

结果则是没有命中二级缓存:

Cache Hit Ratio [com.yefengyu.mybatis.mapper.PersonMapper]: 0.0
Opening JDBC Connection
Sun Jun 16 17:22:38 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Created connection 327177752.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
==>  Preparing: select * from person where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, first_name, last_name, age, email, address
<==        Row: 1, tom, Carine, 25, null, beijing
<==      Total: 1
Person{id=1, firstName='tom', lastName='Carine', age=25, email='null', address='beijing'}
Cache Hit Ratio [com.yefengyu.mybatis.mapper.PersonMapper]: 0.0
Opening JDBC Connection
Sun Jun 16 17:22:38 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Created connection 1589683045.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
==>  Preparing: select * from person where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, first_name, last_name, age, email, address
<==        Row: 1, tom, Carine, 25, null, beijing
<==      Total: 1
Person{id=1, firstName='tom', lastName='Carine', age=25, email='null', address='beijing'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@13805618]
Returned connection 327177752 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
Returned connection 1589683045 to pool.

其它知识点:

     *             和缓存有关的设置/属性:

     *             1)、cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

     *             2)、每个select标签都有useCache="true":

     *                     false:不使用缓存(一级缓存依然使用,二级缓存不使用) :在全局开启的情况下可以禁止部分查询使用二级缓存

     *             3)、【每个增删改标签的:flushCache="true":(一级二级都会清除)】

     *                     增删改执行完成后就会清除缓存;

     *                     测试:flushCache="true":一级缓存就清空了;二级也会被清除;

     *                     查询标签:flushCache="false":

     *                         如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;

     *             4)、sqlSession.clearCache();只是清除当前session的一级缓存;

     *             5)、localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中;

     *                                 STATEMENT:可以禁用一级缓存;

3、外部缓存


外部缓存可以使用第三方提供的缓存包,比如EhCache:

1、首先在类路径下面添加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" />

 <defaultCache
   maxElementsInMemory="10000"
   maxElementsOnDisk="10000000"
   eternal="false"
   overflowToDisk="true"
   timeToIdleSeconds="120"
   timeToLiveSeconds="120"
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>

属性说明:

diskStore:指定数据在磁盘中的存储位置。

defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:

maxElementsInMemory - 在内存中缓存的element的最大数目

maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大

eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断

overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:

timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大

timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大

diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.

diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。

diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作

memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

2、在mapper文件下面使用下面的缓存

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

3、注意依赖包,主要是缓存包,适配包

猜你喜欢

转载自www.cnblogs.com/ye-feng-yu/p/11032253.html