Mybatis 一级缓存作用域是一个sqlsession(缓存属于当前会话独享),即一次sqlsession从打开到关闭,这个过程中,重复的查询语句只执行一次,第一次查询会将结果放入一级缓存,sqlsession关闭之前,重复的查询语句将直接读取缓存。
当发生增删改操作时,在事务commit前会先清空sqlsession的缓存数据(此时清空的只是当前sqlsession的缓存,其他sqlsession已经查询到缓存的数据并不会被清除)。
Mybatis二级缓存的作用域是一个命名空间,即多个sqlsession在使用同一个命名空间下的查询语句时,会先从二级缓存获取,查找不到则从一级缓存获取,再获取不到则从数据库获取。
sqlsession将数据存入二级缓存的时机是在sqlsession关闭后,即当前sqlsession查询了数据,在当前sqlsession关闭前,都不会将数据存入二级缓存。
当sqlsession 更新了数据时,事务commit后依然不会改变二级缓存的数据,只有当前sqlsession关闭后,才会删除当前命名空间内的二级缓存(注意,如果事务提交后未关闭sqlsession,又查询了记录,则关闭sqlsession时,会删除二级缓存,也会将事务提交后的查询结果存入二级缓存)。
例:
开启缓存的配置:
<settings>
<setting name="localCacheScope" value="SESSION"/>
<setting name="cacheEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="LOG4J"/>
settings>
测试代码1(数据库和实体类代码就不贴了):
@Test
public void testCacheWithoutCommit() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
//数据存入一级缓存
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
//会话1读取自己的缓存
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
//会话2关闭,数据存入二级缓存
sqlSession2.close();
//会话1读取二级缓存
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
}
结果1:
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: SELECT id,name,age FROM student WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: id, name, age
TRACE [main] - <== Row: 1, Amy, 16
DEBUG [main] - <== Total: 1
studentMapper1读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: SELECT id,name,age FROM student WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: id, name, age
TRACE [main] - <== Row: 1, Amy, 16
DEBUG [main] - <== Total: 1
studentMapper2读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
studentMapper1读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.25
studentMapper1读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
打印结果中,前两次查询均从数据库获取数据,并存入一级缓存,所以第三条查询中,会话1查询时,没有打印sql语句,是从自己的一级缓存中获取的数据。
第四条查询中,DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.25
,可以看到日志最后有一个0.25
,这是二级缓存的命中率,我们总共查询了四次,只有最后一次是从二级缓存获取数据,所以命中率为0.25。
测试代码2:
@Test
public void testCacheWithCommit() throws Exception {
SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
//数据分别存入两个会话的一级缓存,都会查询一次数据库(第一二次查询)
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
//会话2更新数据,将会话2的一级缓存清空
System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("Tom",1) + "个学生的数据");
//一级缓存已经被清空,重新在从数据库读取数据(第三次查询)
System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
//会话1取到他的一级缓存,数据是修改前的(第四次查询)
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
//会话2关闭,将事务提交后读取的数据存入二级缓存
sqlSession2.close();
//会话1读取二级缓存(第五次查询)
System.out.println("studentMapper1读取数据: " + studentMapper.getStudentById(1));
}
结果2:
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: SELECT id,name,age FROM student WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: id, name, age
TRACE [main] - <== Row: 1, Amy, 16
DEBUG [main] - <== Total: 1
studentMapper1读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: SELECT id,name,age FROM student WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: id, name, age
TRACE [main] - <== Row: 1, Amy, 16
DEBUG [main] - <== Total: 1
studentMapper2读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - ==> Preparing: UPDATE student SET name = ? WHERE id = ?
DEBUG [main] - ==> Parameters: Tom(String), 1(Integer)
DEBUG [main] - <== Updates: 1
studentMapper2更新了1个学生的数据
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
DEBUG [main] - ==> Preparing: SELECT id,name,age FROM student WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Integer)
TRACE [main] - <== Columns: id, name, age
TRACE [main] - <== Row: 1, Tom, 16
DEBUG [main] - <== Total: 1
studentMapper2读取数据: StudentEntity{id=1, name='Tom', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.0
studentMapper1读取数据: StudentEntity{id=1, name='Amy', age=16, className='null'}
DEBUG [main] - Cache Hit Ratio [mapper.StudentMapper]: 0.2
studentMapper1读取数据: StudentEntity{id=1, name='Tom', age=16, className='null'}
从打印结果可以看到,前两次同样是数据库查询,接着进行了数据更新操作(将Amy变成了Tom),此时提交了事务,并清空了会话2的一级缓存,所以在第三次查询时,会话2的一级缓存为空,重新读取数据库数据。
第四次查询是会话1的查询,虽然会话2更新了数据,但会话1读取的数据并未删除,所以读到的是会话1中的一级缓存(即Amy)。
第五次查询,此时会话2已经关闭(因为执行了sqlSession2.close();
),所以会话2在事务提交后的查询结果存入了二级缓存,因此会话1查询到了二级缓存中的Tom。
总共5次查询,仅最后一次查询二级缓存,因此缓存命中率为0.2(Cache Hit Ratio [mapper.StudentMapper]: 0.2
)。
以上仅是两个简单的例子,还有一些特殊情况大家可以自己试试。
比如两个命名空间同时操作一张表,一个命名空间内的会话更新了数据,则该命名空间的二级缓存数据清空,但另一个命名空间的二级缓存没有改变,另一个命名空间中同一条查询语句将会得到未更新的数据。