mybatis緩存


MyBatis 包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定制。緩存可以極大的提升查詢效率。

MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存。

  1、默認情況下,只有一級緩存(SqlSession級別的緩存,也稱為本地緩存)開啟。

  2、二級緩存需要手動開啟和配置,他是基於namespace級別的緩存。

  3、為了提高擴展性。MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存

一級緩存


一級緩存(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();
    }
}

二級緩存


二級緩存(全局緩存):基於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:可以禁用一級緩存;

外部緩存


外部緩存可以使用第三方提供的緩存包,比如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、注意依賴包,主要是緩存包,適配包


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM