MyBatis提供了查询缓存机制,对于大量重复的查询使用缓存可以减轻数据库压力。
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:。
MyBatis的一级缓存是基于会话的,不同会话之间不能共享。
二级缓存是基于namespace的,所有会话均可共享。
一级缓存是默认开启的,而且不能关闭。
MyBatis的配置文件这里就不贴出来了。
public interface DemoDao {
@Select("select * from t where a = 1")
Map<String,Object> selectOne();
@Update("update t set c = 2 where a = 1")
int updateOne();
}
public class CacheDemo {
SqlSessionFactory factory;
@Before
public void init() throws IOException {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(inputStream);
if (inputStream != null) {
inputStream.close();
}
}
@Test
public void test() throws IOException {
SqlSession sqlSession = factory.openSession();
System.out.println("第一次查询...");
select(sqlSession);
System.out.println("第一次修改...");
update();
System.out.println("第二次查询...");
select(sqlSession);
}
//select操作
void select(SqlSession session) throws IOException {
DemoDao mapper = session.getMapper(DemoDao.class);
Map<String, Object> map = mapper.selectOne();
System.out.println(map);
}
//update操作
void update() throws IOException {
SqlSession session = factory.openSession();
DemoDao mapper = session.getMapper(DemoDao.class);
mapper.updateOne();
session.commit();
session.close();
}
}
输出如下:
第一次查询...
DEBUG [main] - ==> Preparing: select * from t where a = 1
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: a, b, c
TRACE [main] - <== Row: 1, 1, 1
DEBUG [main] - <== Total: 1
{a=1, b=1, c=1}
第一次修改...
DEBUG [main] - ==> Preparing: update t set c = 2 where a = 1
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: select * from t where a = 1
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: a, b, c
TRACE [main] - <== Row: 1, 1, 2
DEBUG [main] - <== Total: 1
修改后的结果:{a=1, b=1, c=2}
第二次查询...
{a=1, b=1, c=1}
第一次查询和第二次查询用的是同一个SqlSession,两次查询中间进行了一次UPDATE操作,查询的数据仍然是旧的。
从输出的日志中也可以看到,第二次查询并没有执行SQL,而是从缓存中直接返回的结果。
单独使用MyBatis时,SqlSession的实现类是DefaultSqlSession,在与Spring整合时,MyBatis提供的是SqlSessionTemplate实现类。
SqlSessionTemplate有一个内部类:SqlSessionInterceptor。
源码如下:
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
//每次执行完SQL,都会自动关闭sqlSession
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
每次执行完SQL,SqlSessionInterceptor都会关闭sqlSession。
导致每次执行时,sqlSession都是不一样的,而一级缓存是基于sqlSession的,从而导致缓存失效,每次都要从数据库中查询。