在学习MyBatis时,我一直对进行什么操作会影响数据放进二级缓存的情况感到非常疑惑。由此,我特地对各个情况进行测试分析。特别是在分析SqlSession的commit()和close()方法对二级缓存的影响时,花了我好多的时间。只追求最终结果的朋友,可以直接拉到最后看我的总结。
Mapper:
<mapper namespace="com.ths.demo4.mapper.UserMapper">
<cache>cache>
<sql id="userCols">
${table}.username, ${table}.password, ${table}.sex
sql>
<select id="getUser" resultType="com.ths.demo4.pojo.User" useCache="true">
select id,
<include refid="userCols">
<property name="table" value="user">property>
include>
from jinbaizhe_user as user where user.id = #{id}
select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="user.id">
insert into jinbaizhe_user(username, password, sex) values (#{user.username}, #{user.password}, #{user.sex})
insert>
<update id="updateUser" parameterType="com.ths.demo4.pojo.User">
update jinbaizhe_user set username=#{username}, password=#{password}, sex=#{sex} where id=#{id}
update>
<delete id="deleteUser" parameterType="com.ths.demo4.pojo.User">
delete from jinbaizhe_user where id=#{id}
delete>
<select id="getUserByUsername" resultType="com.ths.demo4.pojo.User" useCache="true">
select id, username, password, sex from jinbaizhe_user where username=#{username}
select>
<select id="getAllUsers" resultType="com.ths.demo4.pojo.User" useCache="true">
select * from jinbaizhe_user
select>
mapper>
测试二级缓存的作用范围:
@Test
@Transactional
@Rollback
public void testCacheLevel2_1(){
//测试二级缓存的作用范围
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println("测试二级缓存的作用范围-------begin");
User user1 = userMapper1.getUser(1);//从数据库中获取,放进sqlSession1的一级缓存中
User user2 = userMapper2.getUser(1);//从数据库中获取,放进sqlSession2的一级缓存中
System.out.println("测试二级缓存的作用范围-------end");
}
控制台输出:
测试二级缓存的作用范围-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
测试二级缓存的作用范围-------end
结论:。二级缓存的作用范围不是SqlSession(已验证),而应该是Mapper(映射器)(这点未验证)。
测试调用SqlSession.close()会将其一级缓存的数据放到二级缓存中:
@Test
@Transactional
@Rollback
public void testCacheLevel2_2(){
//测试SqlSession.close()会将其一级缓存的数据放到二级缓存中
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println("测试SqlSession.close()会将其一级缓存的数据放到二级缓存中-------begin");
User user1 = userMapper1.getUser(1);//查询后放进sqlSession1的一级缓存中
sqlSession1.close();//关闭sqlSession1,会将其中的一级缓存的数据放进二级缓存中。
User user2 = userMapper2.getUser(1);//从二级缓存中获取
System.out.println("测试SqlSession.close()会将其一级缓存的数据放到二级缓存中-------end");
}
控制台输出:
测试SqlSession.close()会将其一级缓存的数据放到二级缓存中-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.5
测试SqlSession.close()会将其一级缓存的数据放到二级缓存中-------end
结论:调用SqlSession.close()
方法后,会将其一级缓存的数据放进二级缓存中。
测试调用SqlSession.commit()
会将其一级缓存的数据放到二级缓存中:
@Test
@Transactional
@Rollback
public void testCacheLevel2_3(){
//测试SqlSession.commit()会将其一级缓存的数据放到二级缓存中
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println("测试SqlSession.commit()会将其一级缓存的数据放到二级缓存中-------begin");
User user1 = userMapper1.getUser(1);//查询后放进sqlSession1的一级缓存中
sqlSession1.commit();//进行commit,会将其中的一级缓存的数据放进二级缓存中,并清空一级缓存。
User user2 = userMapper2.getUser(1);//从二级缓存中获取
System.out.println("测试SqlSession.commit()会将其一级缓存的数据放到二级缓存中-------end");
}
控制台输出:
测试SqlSession.commit()会将其一级缓存的数据放到二级缓存中-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.5
测试SqlSession.commit()会将其一级缓存的数据放到二级缓存中-------end
结论:调用SqlSession.commit()
方法后,会将其一级缓存的数据放进二级缓存中,并清空一级缓存(这点在一级缓存的文章中已证明)。
测试执行更新操作对二级缓存的影响:
@Test
@Transactional
@Rollback
public void testCacheLevel2_4(){
//测试执行更新操作对二级缓存的影响
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
System.out.println("测试执行更新操作对二级缓存的影响-------begin");
User user1 = userMapper1.getUser(1);//查询后放进sqlSession1的一级缓存中
System.out.println("user.sex="+user1.getSex());
sqlSession1.close();//关闭sqlSession1,将一级缓存中的数据放进二级缓存中(使用sqlSession.commit()也能达到同样的效果)
//修改user1
user1.setSex("test");
userMapper2.updateUser(user1);//sqlSession2进行更新操作,会清空自身的一级缓存。(这点在一级缓存的文章中已证明)
//没有执行commit()操作,不会影响二级缓存
User user2 = userMapper3.getUser(1);//还是从二级缓存中获取
System.out.println("user.sex="+user2.getSex());//输出应是原来的"male",而不是"test"
System.out.println("测试执行更新操作对二级缓存的影响-------end");
}
控制台输出:
测试执行更新操作对二级缓存的影响-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
user.sex=male
MyBatis:==> Preparing: update jinbaizhe_user set username=?, password=?, sex=? where id=?
MyBatis:==> Parameters: parker(String), 1(String), test(String), 1(Integer)
MyBatis:<== Updates: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.5
user.sex=male
测试执行更新操作对二级缓存的影响-------end
结论:当对SqlSession执行更新操作(update、delete、insert)时,只会清空其自身的一级缓存,不影响二级缓存。
测试执行更新操作并调用commit()对二级缓存的影响:
@Test
@Transactional
@Rollback
public void testCacheLevel2_5(){
//测试执行更新操作并commit()对二级缓存的影响
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
System.out.println("测试执行更新操作并commit()对二级缓存的影响-------begin");
User user1 = userMapper1.getUser(1);//查询后放进sqlSession1的一级缓存中
System.out.println("user.sex="+user1.getSex());
sqlSession1.close();//关闭sqlSession1,将一级缓存中的数据放进二级缓存中(使用sqlSession.commit()也能达到同样的效果)
//修改user1
user1.setSex("test");
userMapper2.updateUser(user1);//sqlSession2进行更新操作,会清空自身的一级缓存。
//执行commit()操作,清空二级缓存
sqlSession2.commit();
User user2 = userMapper3.getUser(1);//从数据库中获取
System.out.println("user.sex="+user2.getSex());//输出应是"test"
System.out.println("测试执行更新操作并commit()对二级缓存的影响-------end");
}
控制台输出:
测试执行更新操作并commit()对二级缓存的影响-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
user.sex=male
MyBatis:==> Preparing: update jinbaizhe_user set username=?, password=?, sex=? where id=?
MyBatis:==> Parameters: parker(String), 1(String), test(String), 1(Integer)
MyBatis:<== Updates: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
user.sex=test
测试执行更新操作并commit()对二级缓存的影响-------end
结论:当对SqlSession执行更新操作(update、delete、insert)后并执行SqlSession.commit()
时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。
测试执行更新操作并调用close()对二级缓存的影响:
@Test
@Transactional
@Rollback
public void testCacheLevel2_6(){
//测试执行更新操作并close()对二级缓存的影响
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
System.out.println("测试执行更新操作并close()对二级缓存的影响-------begin");
User user1 = userMapper1.getUser(1);//查询后放进sqlSession1的一级缓存中
System.out.println("user.sex="+user1.getSex());
sqlSession1.close();//关闭sqlSession1,将一级缓存中的数据放进二级缓存中(在这里换成SqlSession.commit()也能达到同样的效果)
user1.setSex("test");//修改user1
userMapper2.updateUser(user1);//sqlSession2进行更新操作,会清空自身的一级缓存。
sqlSession2.close();//执行close()操作,此时并没有清空二级缓存。
User user2 = userMapper3.getUser(1);//从二级缓存中获取
System.out.println("user.sex="+user2.getSex());//输出应是"male"
System.out.println("测试执行更新操作并close()对二级缓存的影响-------end");
}
控制台输出:
测试执行更新操作并close()对二级缓存的影响-------begin
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.0
MyBatis:==> Preparing: select id, user.username, user.password, user.sex from jinbaizhe_user as user where user.id = ?
MyBatis:==> Parameters: 1(Integer)
MyBatis:<== Total: 1
user.sex=male
MyBatis:==> Preparing: update jinbaizhe_user set username=?, password=?, sex=? where id=?
MyBatis:==> Parameters: parker(String), 1(String), test(String), 1(Integer)
MyBatis:<== Updates: 1
MyBatis:Cache Hit Ratio [com.ths.demo4.mapper.UserMapper]: 0.5
user.sex=male
测试执行更新操作并close()对二级缓存的影响-------end
结论:当对SqlSession执行更新操作(update、delete、insert)后并执行SqlSession.close()
时,只会清空其自身的一级缓存(执行更新操作的效果),对二级缓存没影响了。
那么问题来了,在执行select操作后,无论是调用SqlSession.commit()还是SqlSession.close(),都能将一级缓存中的数据放到二级缓存中;而在执行更新操作(update、delete、insert)后,调用SqlSession.commit()
和SqlSession.close()
却会有不同的效果,这是为什么呢?
下面先从SqlSession的源码开始分析。
DefaultSqlSession
的部分函数的源码
private final Executor executor;
//DefaultSqlSession的close()
public void close() {
try {
this.executor.close(this.isCommitOrRollbackRequired(false));
this.closeCursors();
this.dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
//DefaultSqlSession的isCommitOrRollbackRequired()
private boolean isCommitOrRollbackRequired(boolean force) {
//由于dirty=true,autoCommit为false,导致函数返回true
return !this.autoCommit && this.dirty || force;//这里很重要,请关注!
}
//DefaultSqlSession的commit()
public void commit() {
this.commit(false);
}
//DefaultSqlSession的commit()
public void commit(boolean force) {
//在我们这个例子中可以将形参force的值当作false,因为实际调用的是上面的commit()函数。
try {
this.executor.commit(this.isCommitOrRollbackRequired(force));
this.dirty = false;
} catch (Exception var6) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6);
} finally {
ErrorContext.instance().reset();
}
}
看完DefaultSqlSession的部分源码,比较一下close()函数和commit()的区别,可以发现最主要的区别还是close()调用了this.executor.close(this.isCommitOrRollbackRequired(false));
,而commit()调用了this.executor.commit(this.isCommitOrRollbackRequired(force));
,所以我们还得继续看executor(CachingExecutor)下的commit()和close()的区别。
CachingExecutor
的部分函数的源码
/*
CacheExecutor有一个重要的属性delegate,它保存的是某类普通的Executor,值在构造时传入。执行数据库update操作时,它直接调用delegate的update方法,执行query方法时先尝试从cache中取值,取不到再调用delegate的查询方法,并将查询结果存入cache中。
每一个SqlSession中都有一个属于自己的Executor,当开启二级缓存后,会使用CachingExecutor来装饰Executor。而装饰者的功能不就是增加功能吗?所以在CachingExecutor类中,与二级缓存有关的操作是不会在它的delegate属性下操作的,而是在它自己的方法里操作,这点对接下来的分析很重要。
*/
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
//CachingExecutor的close()
public void close(boolean forceRollback) {
//由于DefaultSqlSession的isCommitOrRollbackRequired()返回的是true,forceRollback=true。
try {
if (forceRollback) {//条件为true
this.tcm.rollback();//执行了这行代码
} else {
this.tcm.commit();//tcm是TransactionalCacheManager的实例对象,与将一级缓存中的数据添加到二级缓存有关
}
} finally {
this.delegate.close(forceRollback);//由于对delegate的操作与二级缓存并无太大关系,这里就不再分析了。
}
}
//CachingExecutor的commit()
public void commit(boolean required) throws SQLException {
this.delegate.commit(required);//由于delegate的操作与二级缓存并无太大关系,这里就不再分析了。
this.tcm.commit();//tcm是TransactionalCacheManager的实例对象,与将一级缓存中的数据添加到二级缓存有关
}
看到这里,其实答案已经快出来了。只要变量forceRollback
为false
(forceRollback
=!this.autoCommit && this.dirty || force
),那么close()和commit()函数都会去调用this.tcm.commit()
,那么对于二级缓存来说,两者就会有相同的作用效果。想想之前的一个例子:使用mappper查询后,不管是调用commit()还是close(),都会将一级缓存里的数据放进二级缓存中。分析到这里情况应该很清楚了,问题就出在变量forceRollback
的身上。如果对此还有疑问,我们可以继续往下面看对CachingExecutor的属性tcm(TransactionalCacheManager
的实例对象)的分析。
TransactionalCacheManager
的部分函数的源码
public void commit() {
Iterator var1 = this.transactionalCaches.values().iterator();
while(var1.hasNext()) {
TransactionalCache txCache = (TransactionalCache)var1.next();
txCache.commit();//commit()和rollback()仅有的区别
}
}
public void rollback() {
Iterator var1 = this.transactionalCaches.values().iterator();
while(var1.hasNext()) {
TransactionalCache txCache = (TransactionalCache)var1.next();
txCache.rollback();//commit()和rollback()仅有的区别
}
}
问题又变成了关注TransactionalCache
下的commit()和rollback()的区别
TransactionalCache
的部分属性和函数的源码
private final Cache delegate;//存放二级缓存数据的地方。它里面也有一个属性delegate,经过了好几次的装饰,最里面一层有的一个属性名为cache(HashMap类型),是真正存放存放二级缓存数据的地方。
private boolean clearOnCommit;
private final Map
现在再回去重新看CachingExecutor
的分析,是不是更加清楚了呢。
回到前面的问题,SqlSession里的属性dirty为什么变成了true呢?因为没有执行SqlSession.commit()
,又由于是更新操作(update、delete、insert),且autoCommit为false(没有开启自动提交),导致修改后的数据没提交,又与数据库里的数据不一致,那它不就是脏数据(dirty)么?那么从逻辑上分析,既然是脏数据,那就完全没有将脏数据放到二级缓存里的道理。由此看来,调用Sqlsession.close()并不一定会产生与调用Sqlsession.commit()一样的效果。
总结:
1. 进行select操作后,调用SqlSession.close()
方法,会将其一级缓存的数据放进二级缓存中,此时一级缓存随着SqlSession的关闭也就不存在了。
2. 进行select操作后,调用SqlSession.commit()
方法,会将其一级缓存的数据放进二级缓存中,并清空一级缓存(清空一级缓存这点在一级缓存的文章中已说明)。
3. 对SqlSession执行更新操作(update、delete、insert)时,同时不调用SqlSession.commit
或SqlSession.close()
,这时只会清空其自身的一级缓存,对二级缓存没有影响(清空一级缓存这点在一级缓存的文章中已说明)。
4. 对SqlSession执行更新操作(update、delete、insert)后并执行SqlSession.commit()
时,不仅清空其自身的一级缓存(执行更新操作的结果),也清空二级缓存(执行commit()的效果)。
5. 对SqlSession执行更新操作(update、delete、insert)后并执行SqlSession.close()
时(没有执行SqlSession.commit()
),需分两类情况。当autoCommit为false时,只会清空其自身的一级缓存(执行更新操作的效果),对二级缓存没有影响。当autoCommit为true时,会清空二级缓存。
6. 在我们的这几个例子中,close()会不会产生和commit()同样的效果(将数据放进二级缓存中或清空二级缓存),要看SqlSession里的dirty属性,值为flase(即没进行过更新操作),则有同样的效果。若值为true还要看autoCommit的值。换言之,当SqlSession只执行了select操作时,即没有进行过更新操作(update、delete、insert)时,不管是调用SqlSession.commit()还是SqlSession.close(),不管autoCommit是true还是false,都能使一级缓存中的数据放进二级缓存中。当SqlSession执行了update操作后,dirty的值变为true,此时还要看autoCommit的值来决定。更笼统的来说,当forceRollback
=!this.autoCommit && this.dirty || force
的值为false,close()会产生和commit()同样的效果;当其值为false时,两者会有不同的效果。