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

对上面的测试类执行,控制台输出结果如下:

org.apache.ibatis.session.defaults.DefaultSqlSession@15eadca
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();
	}
}

 执行后控制台输出结果如下:

org.apache.ibatis.session.defaults.DefaultSqlSession@15eadca
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,然后重新执行测试用例,结果如下:

org.apache.ibatis.session.defaults.DefaultSqlSession@15eadca
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,重新执行,结果如下:

org.apache.ibatis.session.defaults.DefaultSqlSession@15eadca
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官网中的内容。

猜你喜欢

转载自blog.csdn.net/tony_java_2017/article/details/84205244