也称为一级缓存,分为两个作用域SESSION和STATEMENT。官网中的描述:MyBatis利用本地缓存机制(Local Cache)防止循环引用(循环引用)和加速重复嵌套查询。默认值为SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为声明,本地会话仅用于语句执行上,对相同的一个SqlSession的不同调用将不会共享数据。也就是说本地缓存我们不需要配置就是生效的,生效的作用域默认为SESSION级别下面进行测试:
一本地缓存会话
MyBatis的的的-config.xml中
user.xml
UserMapper.java
import java.util.List;
import org.test.entity.User;
public interface UserMapper {
List selectByOrder(User user);
List 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 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 users1 = userMapper.selectAll();
List 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语句的执行,第二次查的是缓存的数据,现在修改本地缓存的作用域为声明,然后重新执行测试用例,结果如下:
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作用域只是在一个会话内有效,而不同的会话中缓存结果不能共享,假如有些数据库的数据,基本上很少变幻,或者说变化的频率很低,那么我希望所有会话内缓存共享以提高查询效率降低数据库压力,这时候我们就需要使用二级缓存,跨会话的缓存了。
默认情况下是没有开启的,需要手动设置开启,在映射文件中添加一行<缓存/>,修改后如下
然后执行测试类,单元测试失败,控制台显示结果如下:
错误信息显示如下:
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了,也就是说查了三次,两次命中了以上就是简单的二级缓存下面我们继续学习刚才的<缓存/>,该配置项实现的效果是:
以上就是一些默认的缓存相关配置信息,你也可以通过配置项进行修改。
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
大小(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目默认。值是1024。
只读(在只读)属性可以被设置为真或假。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是假的。
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
可以看到全选()方法只有第一次查询的时候执行了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算法:
可以通过eviction属性指定,
3.自定义缓存实现
创建适配器来完全覆盖缓存行为,因为MyBatis的的已经给我们实现了高速缓存接口的实现,二级缓存时通过适配器模式实现的,可以参照LRU或者FIFO的源码进行实现。
/**
* Lru (least recently used) cache decorator
*
* @author Clinton Begin
*/
public class LruCache implements Cache {
private final Cache delegate;
private Map
实现之后,进行配置即可,
4.缓存参照
如果想要某一个mapper xml文件中使用另一个mapper xml文件中的缓存配置,则需要在当前的映射文件中添加如下配置即可
声明:文中很多内容都是参照了的MyBatis的官网中的内容。
下一篇 查询流程以及相关代码的分析