文章目录
1.MyBatis缓存概述1.1.缓存是啥?1.2.MyBatis缓存体系结构1.3.代码结构
2.一级缓存2.1.一级缓存概述2.2.一级缓存的开启:默认开启2.3.一级缓存的存放位置2.4.一级缓存的关闭的方式2.4.1.setting 属性中的flushCache
2.5.一级缓存的案例说明2.5.1.相同SqlSession查询2.5.1.1.代码测试2.5.1.2.控台输出
2.5.2.不同SqlSession查询2.5.2.1.代码测试2.5.2.2.控台输出
2.5.3.相同SqlSession,更新操作会清理缓存2.5.3.1.代码测试2.5.3.2.控台输出
2.5.4.不同SqlSession更新,相同SqlSession查询2.5.4.1.代码测试2.5.4.2.控台输出
2.5.5.案例源码地址
2.6.一级缓存源码使用位置2.6.1.一级缓存源码使用位置-查询2.6.2.一级缓存源码使用位置-更新2.6.3.附:关于上面的key,如何定义
3.二级缓存3.1.二级缓存概述3.2.二级缓存范围与顺序3.3.二级缓存开启 :(默认开启)3.3.1.开启方式:cacheEnabled=true3.3.2.源码code:对上面配置cacheEnabled=true使用3.3.3.二级缓存与一级缓存的查询顺序:先查询二级缓存,再次查询一级缓存
3.4.二级缓存存放位置:CachingExecutor.TransactionalCacheManager3.5.二级缓存的关闭3.5.1.友情提示3.5.2.useCache 属性配置样例3.5.3.友情提示:源码code证明
3.6.二级缓存Cache节点:相关策略介绍typeeviction :LRU,FIFO,SOFT,WEAK
3.7.如何理解二级缓存可以被不同SqlSession共享3.7.1.首先进入CachingExecutor.query方法3.7.2.其次,我们接下来进入tcm.putObject(cache, key, list);分析究竟如何实现3.7.2.1.备注:TransactionalCacheManager tcm 理解3.7.2.2.TransactionalCacheManager.putObject
3.7.3.第三,进入TransactionalCache.putObject方法3.7.3.1.TransactionalCache类源码3.7.3.2.TransactionalCache.putObject
3.7.4.总结
3.8.二级环境案例演示3.8.1.【非必须操作步骤】首先开启二级缓存:cacheEnabled=true 或者不配置该节点 默认使用3.8.2.【必须操作步骤】创建演示二级缓存的方法: BlogSpecMapper.selectBlogListForSecondCacheDemo3.8.3.【必须操作步骤】创建演示二级缓存的SQL:3.8.4.【必须操作步骤】BlogMapper.xml中添加cache节点配置3.8.5.测试验证3.8.5.1.【由于未commit导致无法生效】3.8.5.1.1.【code】3.8.5.1.2.【控台输出】
3.8.5.2.【commit后再次查询】<---由于查询java结果对象未序列化失败3.8.5.2.1.【code】3.8.5.2.2.【控台输出】
3.8.5.3.【Blog对象序列化】
3.8.6.总结
4.至此,Mybatis缓存已经讲解完毕,中间可能有些涉及到篇幅等问题,没有详细描述,欢迎大家留言讨论
1.MyBatis缓存概述
1.1.缓存是啥?
1.通俗的讲:缓存就是临时的数据;
比如:我们下载一款游戏,开始玩的时候,可能比较慢,这个时候这块游戏应用实际上在下载一些实际你选择的游戏场景的资源;
一旦下载完成,你玩了第一局之后,再次玩第二局就能够很快的进入
(暂且不考虑中途游戏应用有突然更新
)游戏中,
这就是因为你的游戏应用已经缓存了当前游戏场景的资源
;
1.2.MyBatis缓存体系结构
1.3.代码结构
2.一级缓存
2.1.一级缓存概述
1.一级缓存也称为本地缓存
2.一级缓存主要是针对的同一个SqlSession对象的缓存
:PerpetualCache localCache
;
如果同一个SqlSession执行的SQL一模一样,会直接使用缓存中的查询结果
2.2.一级缓存的开启:默认开启
2.3.一级缓存的存放位置
2.4.一级缓存的关闭的方式
2.4.1.setting 属性中的flushCache
2.5.一级缓存的案例说明
2.5.1.相同SqlSession查询
2.5.1.1.代码测试
@Test
public void firstLevelDemoSameSqlSession() throws IOException
{
String resource
= "mybatis-config-session-cache-first.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
SqlSession session
= sqlSessionFactory
.openSession();
try {
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog ="+blog
.toString());
Blog blog1
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog1="+blog1
);
BlogMapper mapper1
= session
.getMapper(BlogMapper
.class);
Blog blog2
= mapper1
.selectByPrimaryKey(1L
);
System
.out
.println("blog2="+blog2
);
} finally {
session
.close();
}
}
2.5.1.2.控台输出
2.5.2.不同SqlSession查询
2.5.2.1.代码测试
@Test
public void firstLevelDemoDifferentSqlSession() throws IOException
{
String resource
= "mybatis-config-session-cache-first.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
SqlSession session
= sqlSessionFactory
.openSession();
SqlSession session1
= sqlSessionFactory
.openSession();
try {
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println(blog
.toString());
BlogMapper mapper1
= session1
.getMapper(BlogMapper
.class);
Blog blog1
= mapper1
.selectByPrimaryKey(1L
);
System
.out
.println(blog1
);
} finally {
session
.close();
}
}
2.5.2.2.控台输出
2.5.3.相同SqlSession,更新操作会清理缓存
2.5.3.1.代码测试
@Test
public void firstLevelDemoSameSqlSessionQueryAndUpdate() throws IOException
{
String resource
= "mybatis-config-session-cache-first.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
SqlSession session
= sqlSessionFactory
.openSession();
try {
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog ="+blog
.toString());
Blog updateRecord
=new Blog();
updateRecord
.setBid(blog
.getBid());
updateRecord
.setName("U"+blog
.getName());
mapper
.updateByPrimaryKey(updateRecord
);
session
.commit();
Blog blog1
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog1="+blog1
);
} finally {
session
.close();
}
}
一级缓存是在 BaseExecutor 中的
update()方法中调用
clearLocalCache()清空的 (无条件),query 中会判断。
2.5.3.2.控台输出
1.同一个session中途一旦有更新操作
(update
/delete
),那么一级缓存中的该Session中缓存会清理掉
2.5.4.不同SqlSession更新,相同SqlSession查询
2.5.4.1.代码测试
@Test
public void firstLevelDemoDifferentSqlSessionUpdateAndQuery() throws IOException
{
String resource
= "mybatis-config-session-cache-first.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
SqlSession session
= sqlSessionFactory
.openSession();
try {
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog="+blog
);
SqlSession session1
= sqlSessionFactory
.openSession();
BlogMapper mapper1
= session1
.getMapper(BlogMapper
.class);
Blog updateRecord
=new Blog();
updateRecord
.setBid(1L
);
updateRecord
.setName("frank");
mapper1
.updateByPrimaryKey(updateRecord
);
session1
.commit();
Blog blog1
= mapper
.selectByPrimaryKey(1L
);
System
.out
.println("blog1="+blog1
);
} finally {
session
.close();
}
}
2.5.4.2.控台输出
1.在这个案例中,session进行了第一次查询,返回的name 是gaoxinfu
紧接着,session1进行更新操作,name变为frank
然后,session进行再次查询,由于命中了缓存,所以直接拿了缓存中的内容,name为gaoxinfu
2.这样的话,使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据 可能有不一样的缓存。
在有多个会话或者分布式环境下,会存在脏数据的问题。如果要 解决这个问题,就要用到二级缓存。
2.5.5.案例源码地址
https://gitee.com/gaoxinfu_admin/open-source/blob/master/mybatis/mybatis-3-master/demo-open-source-mybatis/src/main/java/com/gaoxinfu/demo/open/source/mybatis/cache/first/FirstLevelDemo.java
2.6.一级缓存源码使用位置
2.6.1.一级缓存源码使用位置-查询
BaseExecutor.query方法
@SuppressWarnings("unchecked")
@Override
public <E> List
<E> query(MappedStatement ms
, Object parameter
, RowBounds rowBounds
, ResultHandler resultHandler
, CacheKey key
, BoundSql boundSql
) throws SQLException
{
ErrorContext
.instance().resource(ms
.getResource()).activity("executing a query").object(ms
.getId());
if (closed
) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack
== 0 && ms
.isFlushCacheRequired()) {
clearLocalCache();
}
List
<E> list
;
try {
queryStack
++;
list
= resultHandler
== null
? (List
<E>) localCache
.getObject(key
) : null
;
if (list
!= null
) {
handleLocallyCachedOutputParameters(ms
, key
, parameter
, boundSql
);
} else {
list
= queryFromDatabase(ms
, parameter
, rowBounds
, resultHandler
, key
, boundSql
);
}
} finally {
queryStack
--;
}
if (queryStack
== 0) {
for (DeferredLoad deferredLoad
: deferredLoads
) {
deferredLoad
.load();
}
deferredLoads
.clear();
if (configuration
.getLocalCacheScope() == LocalCacheScope
.STATEMENT
) {
clearLocalCache();
}
}
return list
;
}
2.6.2.一级缓存源码使用位置-更新
@Override
public int update(MappedStatement ms
, Object parameter
) throws SQLException
{
ErrorContext
.instance().resource(ms
.getResource()).activity("executing an update").object(ms
.getId());
if (closed
) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms
, parameter
);
}
@Override
public void clearLocalCache() {
if (!closed
) {
localCache
.clear();
localOutputParameterCache
.clear();
}
}
同一个session中途一旦有更新操作
(update
/delete
),那么一级缓存中的该Session中缓存会清理掉
2.6.3.附:关于上面的key,如何定义
3.二级缓存
3.1.二级缓存概述
1.从上面的”不同SqlSession更新,Session相同查询“中我们知道,一级缓存存在一个问题:一级缓存不能跨会话共享数据;
2.二级缓存主要是解决上面一级缓存中的问题
(一级缓存不能共享数据
),
3.二级缓存的有效范围是namespace级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享)
;
3.2.二级缓存范围与顺序
1.二级缓存,相同的Namespace,由于不同的SqlSession的数据可以进行数据的共享
因此,二级缓存的查询范围是要大于一级缓存的
;
3.3.二级缓存开启 :(默认开启)
3.3.1.开启方式:cacheEnabled=true
<!-- 控制全局缓存(二级缓存)
-->
<setting name
="cacheEnabled" value
="true"/>
3.3.2.源码code:对上面配置cacheEnabled=true使用
Configuration.newExecutor方法
public Executor
newExecutor(Transaction transaction
, ExecutorType executorType
) {
executorType
= executorType
== null
? defaultExecutorType
: executorType
;
executorType
= executorType
== null
? ExecutorType
.SIMPLE
: executorType
;
Executor executor
;
if (ExecutorType
.BATCH
== executorType
) {
executor
= new BatchExecutor(this, transaction
);
} else if (ExecutorType
.REUSE
== executorType
) {
executor
= new ReuseExecutor(this, transaction
);
} else {
executor
= new SimpleExecutor(this, transaction
);
}
if (cacheEnabled
) {
executor
= new CachingExecutor(executor
);
}
executor
= (Executor
) interceptorChain
.pluginAll(executor
);
return executor
;
}
3.3.3.二级缓存与一级缓存的查询顺序:先查询二级缓存,再次查询一级缓存
作为一个作用范围更广的缓存,它肯定是在 SqlSession 的外层,否则不可能被多个 SqlSession 共享。
而一级缓存是在 SqlSession 内部的,肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。
3.4.二级缓存存放位置:CachingExecutor.TransactionalCacheManager
1.对于二级缓存的处理,有一个装饰器类CachingExecutor 进行特殊的处理,
如果配置中已经开启了二级缓存,那么创建 Executor 对象的时候会对通过CachingExecutor对Executor进行装饰。
2.CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,
如果没有委派交给真正的查询器Executor实现类,比如 SimpleExecutor来执行查询,再走到一级缓存的流程。
最后会把结果缓存起来,并且返回给用户。
3.5.二级缓存的关闭
3.5.1.友情提示
1.这里注意下,虽然,我们上面配置了cacheEnabled
=true,但是并不意味着你的二级缓存已经生效,只是说我们在进行Executor选择的时候会使用
CachingExecutor
;
2.因为具体你的二级缓存有么有生效,取决于你的每一个SQL中有没有配置 useCache这个属性
,如配置为
false,那么二级缓存不会生效
3.5.2.useCache 属性配置样例
<select id
="selectBlogList" resultMap
="BaseResultMap" useCache
="true">
select bid
, name
, author_id authorId from blog
</select
>
3.5.3.友情提示:源码code证明
CachingExecutor.query
@Override
public <E> List
<E> query(MappedStatement ms
, Object parameterObject
, RowBounds rowBounds
, ResultHandler resultHandler
, CacheKey key
, BoundSql boundSql
)
throws SQLException
{
Cache cache
= ms
.getCache();
if (cache
!= null
) {
flushCacheIfRequired(ms
);
if (ms
.isUseCache() && resultHandler
== null
) {
ensureNoOutParams(ms
, boundSql
);
@SuppressWarnings("unchecked")
List
<E> list
= (List
<E>) tcm
.getObject(cache
, key
);
if (list
== null
) {
list
= delegate
.query(ms
, parameterObject
, rowBounds
, resultHandler
, key
, boundSql
);
tcm
.putObject(cache
, key
, list
);
}
return list
;
}
}
return delegate
.query(ms
, parameterObject
, rowBounds
, resultHandler
, key
, boundSql
);
}
3.6.二级缓存Cache节点:相关策略介绍
type
eviction :LRU,FIFO,SOFT,WEAK
1.这里你具体配置那个,其实实际上代表这一个回收策略处理的类,mybatis配置文件解析的时候会进行解析,对应类处理
3.7.如何理解二级缓存可以被不同SqlSession共享
3.7.1.首先进入CachingExecutor.query方法
@Override
public <E> List
<E> query(MappedStatement ms
, Object parameterObject
, RowBounds rowBounds
, ResultHandler resultHandler
, CacheKey key
, BoundSql boundSql
)
throws SQLException
{
Cache cache
= ms
.getCache();
if (cache
!= null
) {
flushCacheIfRequired(ms
);
if (ms
.isUseCache() && resultHandler
== null
) {
ensureNoOutParams(ms
, boundSql
);
@SuppressWarnings("unchecked")
List
<E> list
= (List
<E>) tcm
.getObject(cache
, key
);
if (list
== null
) {
list
= delegate
.query(ms
, parameterObject
, rowBounds
, resultHandler
, key
, boundSql
);
tcm
.putObject(cache
, key
, list
);
}
return list
;
}
}
return delegate
.query(ms
, parameterObject
, rowBounds
, resultHandler
, key
, boundSql
);
}
3.7.2.其次,我们接下来进入tcm.putObject(cache, key, list);分析究竟如何实现
3.7.2.1.备注:TransactionalCacheManager tcm 理解
1.CachingExecutor 中的TransactionalCacheManager 只是缓存的一个管理工具,
或者你就认为他就是一个Util类而已,并不会存储任何东西
public class CachingExecutor implements Executor {
private final Executor delegate
;
private final TransactionalCacheManager tcm
= new TransactionalCacheManager();
public CachingExecutor(Executor delegate
) {
this.delegate
= delegate
;
delegate
.setExecutorWrapper(this);
}
}
3.7.2.2.TransactionalCacheManager.putObject
public void putObject(Cache cache
, CacheKey key
, Object value
) {
getTransactionalCache(cache
).putObject(key
, value
);
}
private TransactionalCache
getTransactionalCache(Cache cache
) {
return transactionalCaches
.computeIfAbsent(cache
, TransactionalCache
::new);
}
@Override
public void putObject(Object key
, Object object
) {
entriesToAddOnCommit
.put(key
, object
);
}
3.7.3.第三,进入TransactionalCache.putObject方法
3.7.3.1.TransactionalCache类源码
package org
.apache
.ibatis
.cache
.decorators
;
import java
.util
.HashMap
;
import java
.util
.HashSet
;
import java
.util
.Map
;
import java
.util
.Set
;
import java
.util
.concurrent
.locks
.ReadWriteLock
;
import org
.apache
.ibatis
.cache
.Cache
;
import org
.apache
.ibatis
.logging
.Log
;
import org
.apache
.ibatis
.logging
.LogFactory
;
public class TransactionalCache implements Cache {
private static final Log log
= LogFactory
.getLog(TransactionalCache
.class);
private final Cache delegate
;
private boolean clearOnCommit
;
private final Map
<Object, Object> entriesToAddOnCommit
;
private final Set
<Object> entriesMissedInCache
;
public TransactionalCache(Cache delegate
) {
this.delegate
= delegate
;
this.clearOnCommit
= false;
this.entriesToAddOnCommit
= new HashMap<>();
this.entriesMissedInCache
= new HashSet<>();
}
@Override
public String
getId() {
return delegate
.getId();
}
@Override
public int getSize() {
return delegate
.getSize();
}
@Override
public Object
getObject(Object key
) {
Object object
= delegate
.getObject(key
);
if (object
== null
) {
entriesMissedInCache
.add(key
);
}
if (clearOnCommit
) {
return null
;
} else {
return object
;
}
}
@Override
public ReadWriteLock
getReadWriteLock() {
return null
;
}
@Override
public void putObject(Object key
, Object object
) {
entriesToAddOnCommit
.put(key
, object
);
}
@Override
public Object
removeObject(Object key
) {
return null
;
}
@Override
public void clear() {
clearOnCommit
= true;
entriesToAddOnCommit
.clear();
}
public void commit() {
if (clearOnCommit
) {
delegate
.clear();
}
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit
= false;
entriesToAddOnCommit
.clear();
entriesMissedInCache
.clear();
}
private void flushPendingEntries() {
for (Map
.Entry
<Object, Object> entry
: entriesToAddOnCommit
.entrySet()) {
delegate
.putObject(entry
.getKey(), entry
.getValue());
}
for (Object entry
: entriesMissedInCache
) {
if (!entriesToAddOnCommit
.containsKey(entry
)) {
delegate
.putObject(entry
, null
);
}
}
}
private void unlockMissedEntries() {
for (Object entry
: entriesMissedInCache
) {
try {
delegate
.removeObject(entry
);
} catch (Exception e
) {
log
.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e
);
}
}
}
}
3.7.3.2.TransactionalCache.putObject
private final Map
<Object, Object> entriesToAddOnCommit
;
@Override
public void putObject(Object key
, Object object
) {
entriesToAddOnCommit
.put(key
, object
);
}
3.7.4.总结
我们最终将数据put到了
TransactionalCache(其本质是MapperStatement中的cache
);
所以我们下次,不同的SqlSession调用同一个MapperStatement 缓存是可以共享的
3.8.二级环境案例演示
3.8.1.【非必须操作步骤】首先开启二级缓存:cacheEnabled=true 或者不配置该节点 默认使用
<setting name
="cacheEnabled" value
="ture"/>
3.8.2.【必须操作步骤】创建演示二级缓存的方法: BlogSpecMapper.selectBlogListForSecondCacheDemo
List
<Blog> selectBlogListForSecondCacheDemo(RowBounds rowBounds
);
3.8.3.【必须操作步骤】创建演示二级缓存的SQL:
<select id
="selectBlogListForSecondCacheDemo" resultMap
="BaseResultMap" useCache
="true">
select bid
, name
, author_id authorId from blog
</select
>
3.8.4.【必须操作步骤】BlogMapper.xml中添加cache节点配置
如果使用二级缓存,在namespace中是必须要配置的
<cache type
="org.apache.ibatis.cache.impl.PerpetualCache" >
<property name
="size" value
="1024"/><!--1 hour
-->
<property name
="eviction" value
="LRU"/><!--1 hour
-->
<property name
="flushInterval" value
="120000"/>
<property name
="readOnly" value
="false"/>
</cache
>
3.8.5.测试验证
3.8.5.1.【由于未commit导致无法生效】
3.8.5.1.1.【code】
@Test
public void testNotCommit() throws IOException
{
String resource
= "mybatis-config-session-cache-second.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
try {
SqlSession session
= sqlSessionFactory
.openSession();
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKeyForSecondCache(1L
);
System
.out
.println("blog ="+blog
.toString());
SqlSession session1
= sqlSessionFactory
.openSession();
BlogMapper mapper1
= session1
.getMapper(BlogMapper
.class);
Blog blog1
= mapper1
.selectByPrimaryKeyForSecondCache(1L
);
System
.out
.println("blog1 ="+blog1
.toString());
} finally {
}
}
3.8.5.1.2.【控台输出】
3.8.5.2.【commit后再次查询】<—由于查询java结果对象未序列化失败
3.8.5.2.1.【code】
@Test
public void testAfterCommit() throws IOException
{
String resource
= "mybatis-config-session-cache-second.xml";
InputStream inputStream
= Resources
.getResourceAsStream(resource
);
SqlSessionFactory sqlSessionFactory
= new SqlSessionFactoryBuilder().build(inputStream
);
try {
SqlSession session
= sqlSessionFactory
.openSession();
BlogMapper mapper
= session
.getMapper(BlogMapper
.class);
Blog blog
= mapper
.selectByPrimaryKeyForSecondCache(1L
);
System
.out
.println("blog ="+blog
.toString());
session
.commit();
SqlSession session1
= sqlSessionFactory
.openSession();
BlogMapper mapper1
= session1
.getMapper(BlogMapper
.class);
Blog blog1
= mapper1
.selectByPrimaryKeyForSecondCache(1L
);
System
.out
.println("blog1 ="+blog1
.toString());
} finally {
}
}
3.8.5.2.2.【控台输出】
Opening JDBC Connection
Thu Jul
09 14:18:25 CST
2020 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
892529689.
Setting autocommit to
false on JDBC Connection
[com
.mysql
.jdbc
.JDBC4Connection
@3532ec19]
==> Preparing
: select bid
, name
, author_id from blog where bid
= ?
==> Parameters
: 1(Long
)
<== Columns
: bid
, name
, author_id
<== Row
: 1, frank
, null
<== Total
: 1
blog
=Blog
{bid
=1, name
='frank', authorId
=null
,hashCode
= 1418621776}
org
.apache
.ibatis
.exceptions
.PersistenceException
:
### Error committing transaction
. Cause
: org
.apache
.ibatis
.cache
.CacheException
: Error serializing object
. Cause
: java
.io
.NotSerializableException
: com
.gaoxinfu
.demo
.open
.source
.mybatis
.db
.model
.Blog
### Cause
: org
.apache
.ibatis
.cache
.CacheException
: Error serializing object
. Cause
: java
.io
.NotSerializableException
: com
.gaoxinfu
.demo
.open
.source
.mybatis
.db
.model
.Blog
at org
.apache
.ibatis
.exceptions
.ExceptionFactory
.wrapException(ExceptionFactory
.java
:30)
at org
.apache
.ibatis
.session
.defaults
.DefaultSqlSession
.commit(DefaultSqlSession
.java
:229)
at org
.apache
.ibatis
.session
.defaults
.DefaultSqlSession
.commit(DefaultSqlSession
.java
:220)
at com
.gaoxinfu
.demo
.open
.source
.mybatis
.cache
.second
.SecondLevelDemo
.testAfterCommit(SecondLevelDemo
.java
:55)
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
:498)
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
.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
.junit
.runner
.JUnitCore
.run(JUnitCore
.java
:137)
at com
.intellij
.junit4
.JUnit4IdeaTestRunner
.startRunnerWithArgs(JUnit4IdeaTestRunner
.java
:68)
at com
.intellij
.rt
.execution
.junit
.IdeaTestRunner$Repeater
.startRunnerWithArgs(IdeaTestRunner
.java
:47)
at com
.intellij
.rt
.execution
.junit
.JUnitStarter
.prepareStreamsAndStart(JUnitStarter
.java
:242)
at com
.intellij
.rt
.execution
.junit
.JUnitStarter
.main(JUnitStarter
.java
:70)
Caused by
: org
.apache
.ibatis
.cache
.CacheException
: Error serializing object
. Cause
: java
.io
.NotSerializableException
: com
.gaoxinfu
.demo
.open
.source
.mybatis
.db
.model
.Blog
at org
.apache
.ibatis
.cache
.decorators
.SerializedCache
.serialize(SerializedCache
.java
:100)
at org
.apache
.ibatis
.cache
.decorators
.SerializedCache
.putObject(SerializedCache
.java
:56)
at org
.apache
.ibatis
.cache
.decorators
.LoggingCache
.putObject(LoggingCache
.java
:54)
at org
.apache
.ibatis
.cache
.decorators
.SynchronizedCache
.putObject(SynchronizedCache
.java
:45)
at org
.apache
.ibatis
.cache
.decorators
.TransactionalCache
.flushPendingEntries(TransactionalCache
.java
:140)
at org
.apache
.ibatis
.cache
.decorators
.TransactionalCache
.commit(TransactionalCache
.java
:122)
at org
.apache
.ibatis
.cache
.TransactionalCacheManager
.commit(TransactionalCacheManager
.java
:53)
at org
.apache
.ibatis
.executor
.CachingExecutor
.commit(CachingExecutor
.java
:126)
at org
.apache
.ibatis
.session
.defaults
.DefaultSqlSession
.commit(DefaultSqlSession
.java
:226)
... 24 more
Caused by
: java
.io
.NotSerializableException
: com
.gaoxinfu
.demo
.open
.source
.mybatis
.db
.model
.Blog
at java
.io
.ObjectOutputStream
.writeObject0(ObjectOutputStream
.java
:1184)
at java
.io
.ObjectOutputStream
.writeObject(ObjectOutputStream
.java
:348)
at java
.util
.ArrayList
.writeObject(ArrayList
.java
:766)
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
:498)
at java
.io
.ObjectStreamClass
.invokeWriteObject(ObjectStreamClass
.java
:1140)
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
:96)
... 32 more
Process finished with exit code
255
3.8.5.3.【Blog对象序列化】
再次执行testAfterCommit方法
3.8.6.总结
1.我们这里之所以查询后提交,一般没有这样操作的,只是我们这里相当于模拟了一下update等操作
如果其他的session进行更新了数据之后,必然会进行commit,
所以我们能够共享到其他session的数据
putObject操作只是将缓存加入到map中,并没有实际性的提交,只有实际性的调用commit方法之后,才会进入
TransactionalCacheManager.commit()
public void commit() {
for (TransactionalCache txCache
: transactionalCaches
.values()) {
txCache
.commit();
}
}
TransactionalCache.commit()
public void commit() {
if (clearOnCommit
) {
delegate
.clear();
}
flushPendingEntries();
reset();
}
4.至此,Mybatis缓存已经讲解完毕,中间可能有些涉及到篇幅等问题,没有详细描述,欢迎大家留言讨论