1. 程式人生 > >MyBatis 本地快取和二級快取使用以及原始碼分析 第一篇

MyBatis 本地快取和二級快取使用以及原始碼分析 第一篇

本地快取

   也稱為一級快取,分為兩個作用域SESSION和STATEMENT。官網中的描述:MyBatis利用本地快取機制(Local Cache)防止迴圈引用(迴圈引用)和加速重複巢狀查詢。預設值為SESSION,這種情況下會快取一個會話中執行的所有查詢。若設定值為STATEMENT,本地會話僅用於語句執行上,對相同SqlSession的不同調用將不會共享資料。也就是說本地快取我們不需要配置就是生效的,生效的作用域預設為SESSION級別下面進行測試:

一本地快取會話

    MyBatis的-config.xml中

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="logImpl" value="LOG4J"/>
	</settings>
	<typeAliases>
		<package name="org.test.entity" />
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url"
					value="jdbc:mysql://localhost:3306/spring_test?useSSL=true" />
				<property name="username" value="root" />
				<property name="password" value="root" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="org/test/mapper/user.xml" />
	</mappers>
</configuration>

 user.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
<mapper namespace="org.test.mapper.UserMapper">
	
    <select id="selectAll"  resultType="User">
        select * from tb_oder
    </select>
    
</mapper>

UserMapper.java

import java.util.List;

import org.test.entity.User;

public interface UserMapper {
	List<User> selectByOrder(User user);
	List<User> selectAll();
}

 

測試類

public class TestSelectAll {
	SqlSessionFactory  sessionFactory;

	@Before
	public void init() throws IOException {
		org.apache.ibatis.logging.LogFactory.useLog4JLogging();
		String conf = "SqlMapConfig.xml";
		Reader reader = Resources.getResourceAsReader(conf);
	    sessionFactory = new SqlSessionFactoryBuilder().build(reader);
	}
	
	@Test
	public void testSelectAll() throws IOException {
		SqlSession session = sessionFactory.openSession(true);
		System.out.println(session);
		UserMapper userMapper =session.getMapper(UserMapper.class);
		List<User> users = userMapper.selectAll();
		session.close();
	}
}

log4j.properties 

# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.org.test.mapper.UserMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

對上面的測試類執行,控制檯輸出結果如下:

[email protected]
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4

打印出執行的SQL語句以及結果,可以看出執行了一個查詢語句。現在我們在同一個的SqlSession中進行兩次查詢,測試類修改後如下

public class TestSelectAll {
	SqlSessionFactory  sessionFactory;

	@Before
	public void init() throws IOException {
		org.apache.ibatis.logging.LogFactory.useLog4JLogging();
		String conf = "SqlMapConfig.xml";
		Reader reader = Resources.getResourceAsReader(conf);
	    sessionFactory = new SqlSessionFactoryBuilder().build(reader);
	}
	
	@Test
	public void testSelectAll() throws IOException {
		SqlSession session = sessionFactory.openSession(true);
		System.out.println(session);
		UserMapper userMapper =session.getMapper(UserMapper.class);
		List<User> users1 = userMapper.selectAll();
		List<User> users2 = userMapper.selectAll();
		session.close();
	}
}

 執行後控制檯輸出結果如下:

[email protected]
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4

發現SQL日誌和上面一樣,也就是說只進行了一次的SQL語句的執行,第二次查的是快取的資料,現在修改本地快取的作用域為STATEMENT,然後重新執行測試用例,結果如下:

[email protected]
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4

修改後的配置檔案如下:

 

現在我們再將本地快取的作用域修改為SESSION,重新執行,結果如下:

[email protected]
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4

結果和第一次執行相同,也就是說只執行了一次SQL,第二次查詢只是從快取中取出了結果。現在將程式碼修改為從不同的會話中取出結果,那麼執行如何?

測試類的修改:

執行結果如下:

***************session1*****************************
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
**************session2******************************
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4

可以看出查詢了兩次,因為本地快取的SESSION作用域只是在一個會話內有效,而不同的會話中快取結果不能共享,假如有些資料庫的資料,基本上很少變幻,或者說變化的頻率很低,那麼我希望所有會話內快取共享以提高查詢效率降低資料庫壓力,這時候我們就需要使用二級快取,跨會話的快取了。 

二級快取

預設情況下是沒有開啟的,需要手動設定開啟,在對映檔案中新增一行<快取/>,修改後如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
<mapper namespace="org.test.mapper.UserMapper">
	<cache/>
    <select id="selectAll"  resultType="User">
        select * from tb_oder
    </select>
    
</mapper>

然後執行測試類,單元測試失敗,控制檯顯示結果如下:

錯誤資訊顯示如下:

org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: org.test.entity.User
	at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:102)
	at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:56)
	at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:51)
	at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:45)
	at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:122)
	at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:105)
	at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:44)
	at org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor.java:61)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession.java:264)
	at org.test.test.TestSelectAll.testSelectAll(TestSelectAll.java:34)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.io.NotSerializableException: org.test.entity.User
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at java.util.ArrayList.writeObject(ArrayList.java:747)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:483)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:988)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:97)
	... 33 more

分析:日誌顯示session1相關的程式碼執行成功,因為顯示了sql和結果集,不過多了一行Cache Hit Ratio [org.test.mapper.UserMapper]:0.0,這個是快取命中率,就是說當前的快取被多少次查詢命中了,但是會話2中的程式碼沒有執行,因為在執行完會話1的程式碼後,會將會話1的結果快取起來,現在快取失敗了,原因就是沒有對使用者進行序列化。現在對使用者進行序列化,序列化後重新執行測試類,結果如下:

***************session1*****************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
**************session2******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.5

執行成功,只執行了一次的SQL查詢,在會話1中進行查詢前,快取命中率為0.0,會話1中查詢後,快取中有了結果集,會話2中查詢時,在快取中發現了已經有該查詢的記錄,就直接從快取中獲取結果,就不去資料庫查詢,同時顯示快取命中率為0.5那麼我們再增加一個會議3的查詢看看快取命中率:

***************session1*****************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
**************session2******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.5
**************session3******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.6666666666666666

快取命中率變為2/3了,也就是說查了三次,兩次命中了以上就是簡單的二級快取下面我們繼續學習剛才的<cache/>,該配置項實現的效果是。:

  • 對映語句檔案中的所有select語句將會被快取。
  • 對映語句檔案中的所有insert,update和delete語句會重新整理快取。
  • 快取會使用最近最少使用(LRU,最近最少使用的)演算法來收回。
  • 根據時間表(比如no Flush Interval,沒有重新整理間隔),快取不會以任何時間順序來重新整理。
  • 快取會儲存列表集合或物件(無論查詢方法返回什麼)的1024個引用。
  • 快取會被視為是讀/寫(可讀/可寫)的快取,意味著物件檢索不是共享的,而且可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。

以上就是一些預設的快取相關配置資訊,你也可以通過配置項進行修改。

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

flushInterval(重新整理間隔)可以被設定為任意的正整數,而且它們代表一個合理的毫秒 形式的時間段。預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。

size(引用數目)可以被設定為任意正整數,要記住你快取的物件數目和你執行環境的 可用記憶體資源數目。預設值是 1024。

readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回緩 存物件的相同例項。因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取 會返回快取物件的拷貝(通過序列化) 。這會慢一些,但是安全,因此預設是 false。

1.flushCache =“false”useCache =“true”你可以通過修改這兩個配置在查詢的語句中來修改是否重新整理快取和使用快取,可以使用flushCache在增加改的語句中配置是否重新整理快取。現在修改user.xml,新增一個selectByName方法但是不適用快取,程式碼如下,

public class TestSelectAll {
	SqlSessionFactory  sessionFactory;

	@Before
	public void init() throws IOException {
		org.apache.ibatis.logging.LogFactory.useLog4JLogging();
		String conf = "SqlMapConfig.xml";
		Reader reader = Resources.getResourceAsReader(conf);
	    sessionFactory = new SqlSessionFactoryBuilder().build(reader);
	}
	
	@Test
	public void testSelectAll() throws IOException {
		System.out.println("***************session1*****************************");
		SqlSession session1 = sessionFactory.openSession(true);
		UserMapper userMapper =session1.getMapper(UserMapper.class);
		userMapper.selectAll();
		userMapper.selectByName("j");
		session1.close();
		System.out.println("**************session2******************************");
		SqlSession session2 = sessionFactory.openSession(true);
		UserMapper userMapper2 =session2.getMapper(UserMapper.class);
		userMapper2.selectAll();
		userMapper2.selectByName("j");
		session2.close();
		System.out.println("**************session3******************************");
		SqlSession session3= sessionFactory.openSession(true);
		UserMapper userMapper3 =session3.getMapper(UserMapper.class);
		userMapper3.selectAll();
		userMapper3.selectByName("j");
		session3.close();
	}
}

然後我們看看執行效果

***************session1*****************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
DEBUG [main] - ==>  Preparing: select * from tb_oder where name like concat(?,'%') 
DEBUG [main] - ==> Parameters: j(String)
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 3, 39, joe
DEBUG [main] - <==      Total: 2
**************session2******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.5
DEBUG [main] - ==>  Preparing: select * from tb_oder where name like concat(?,'%') 
DEBUG [main] - ==> Parameters: j(String)
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 3, 39, joe
DEBUG [main] - <==      Total: 2
**************session3******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.6666666666666666
DEBUG [main] - ==>  Preparing: select * from tb_oder where name like concat(?,'%') 
DEBUG [main] - ==> Parameters: j(String)
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 3, 39, joe
DEBUG [main] - <==      Total: 2

可以看到selectAll()方法只有第一次查詢的時候執行了SQL,第二次和第三次都沒有;而selectByName()每次都執行了SQL,也就是說它沒有走快取。

設定快取重新整理:我們新增一個根據名字更新年齡的更新語句,然後使用預設的配置看看查詢情況;

public class TestSelectAll {
	SqlSessionFactory  sessionFactory;

	@Before
	public void init() throws IOException {
		org.apache.ibatis.logging.LogFactory.useLog4JLogging();
		String conf = "SqlMapConfig.xml";
		Reader reader = Resources.getResourceAsReader(conf);
	    sessionFactory = new SqlSessionFactoryBuilder().build(reader);
	}
	
	@Test
	public void testSelectAll() throws IOException {
		System.out.println("***************session1*****************************");
		SqlSession session1 = sessionFactory.openSession(true);
		UserMapper userMapper =session1.getMapper(UserMapper.class);
		userMapper.selectAll();
//		userMapper.selectByName("j");
		session1.close();
		System.out.println("**************session2******************************");
		SqlSession session2 = sessionFactory.openSession(true);
		UserMapper userMapper2 =session2.getMapper(UserMapper.class);
		System.out.println(userMapper2.selectAll());
//		userMapper2.selectByName("j");
		session2.close();
		System.out.println("**************session3******************************");
		SqlSession session3= sessionFactory.openSession(true);
		UserMapper userMapper3 =session3.getMapper(UserMapper.class);
		User user = new User();
		user.setName("rose");
		user.setAge((byte) 16);
		userMapper3.updateAge(user);
		System.out.println(userMapper3.selectAll());
//		userMapper3.selectByName("j");
		session3.close();
		System.out.println("**************session4******************************");
		SqlSession session4= sessionFactory.openSession(true);
		UserMapper userMapper4 =session4.getMapper(UserMapper.class);
		System.out.println(userMapper4.selectAll());
//		userMapper3.selectByName("j");
		session4.close();
	}
}

執行測試用例日子顯示如下:

***************session1*****************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
**************session2******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.5
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=16], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]
**************session3******************************
DEBUG [main] - ==>  Preparing: update tb_oder set age=? where name =? 
DEBUG [main] - ==> Parameters: 23(Byte), rose(String)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.6666666666666666
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 23, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=23], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]
**************session4******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.75
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=23], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]

在更新前上漲的年齡是16,更新後變為23,更新後的查詢快取中的結果也都是23了,也就是重新整理了快取。如果修改更新中的配置flushCache = “false”,重新執行測試用例

***************session1*****************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.0
DEBUG [main] - ==>  Preparing: select * from tb_oder 
DEBUG [main] - ==> Parameters: 
TRACE [main] - <==    Columns: id, age, name
TRACE [main] - <==        Row: 1, 23, jack
TRACE [main] - <==        Row: 2, 16, rose
TRACE [main] - <==        Row: 3, 39, joe
TRACE [main] - <==        Row: 4, 8, lucy
DEBUG [main] - <==      Total: 4
**************session2******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.5
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=16], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]
**************session3******************************
DEBUG [main] - ==>  Preparing: update tb_oder set age=? where name =? 
DEBUG [main] - ==> Parameters: 23(Byte), rose(String)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.6666666666666666
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=16], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]
**************session4******************************
DEBUG [main] - Cache Hit Ratio [org.test.mapper.UserMapper]: 0.75
[User [id=1, name=jack, age=23], User [id=2, name=rose, age=16], User [id=3, name=joe, age=39], User [id=4, name=lucy, age=8]]

可以看到更新之後快取查詢出的結果並沒有使用更新後的資料,也就是說該更新語句沒有更新快取。

2.快取演算法

MyBatis提供了四種快取演算法,預設使用LRU演算法:

  • LRU – 最近最少使用的:移除最長時間不被使用的物件
  • FIFO – 先進先出:按物件進入快取的順序來移除它們
  • SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件
  • WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件

可以通過eviction屬性指定,<cache eviction="FIFO"/> 以上快取演算法不能滿足我們的需求時我們還可以自定義快取實現

3.自定義快取實現

 建立介面卡來完全覆蓋快取行為,因為MyBatis已經給我們實現了Cache介面的實現,二級快取時通過介面卡模式實現的,可以參照LRU或者FIFO的原始碼進行實現。

/**
 * Lru (least recently used) cache decorator
 *
 * @author Clinton Begin
 */
public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

實現之後,進行配置即可,<cache type="com.domain.something.MyCustomCache"/> ,自定義實現的快取也可以指定屬性或者在屬性設定完畢之後在呼叫一個初始化方法(從3.4.2版本開始),那麼需要實現org.apache.ibatis.builder.InitializingObject介面。

4.快取參照

如果想要某一個mapper xml檔案中使用另一個mapper xml 檔案中的快取配置,則需要在當前的對映檔案中新增如下配置即可

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

宣告:文中很多內容都是參照了MyBatis官網中的內容。