在上一次整合完spring和mybatis之后,我们已经了解了mybatis整合spring的底层原理和执行流程,尤其是知道了mapperScan的扫描原理,接下来就要开始使用mybatis,在之前我阅读mybatis源码的时候我们知道mybatis的一级缓存是默认开启的,当连续执行两次查询的时候,mybatis会在同一个会话中的第一次查询的时候会把查询结果放到一级缓存中,当执行第二次查询的时候可以直接从缓存中返回查好的值,那么当spring整合mybatis之后,一级缓存还会像之前那样起作用吗,带着这样的疑问,我开始了实验
public class MybatisTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class);
UserService service = (UserService) configApplicationContext.getBean(UserService.class);
//由于之前已经配置好了log4j日志,在这里我就调用两次查询方法,来查看日志中sql语句的打印情况
System.out.println(service.getUser(1));
System.out.println(service.getUser(1));
}
}
可以看到这里输出了两句sql语句,所以判断一级缓存在整合spring后是失效的,那么我们现在就要找出一级缓存失效的原因
@Service
public class UserService{
@Autowired
UserMapper mapper;
public User getUser(int id) {
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
return mapper.selectUser(id);
}
}
把断点打在selectUser方法上,断点进入,在上一篇文中我们讲到了mapper最终会被MapperProxy类所代理,所以执行mapper中的查询方法会被代理类所代理,会执行代理类的invoke方法,我们看下这个invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//把mapper中的方法缓存
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用代理方法执行
return mapperMethod.execute(sqlSession, args);
}
我们一路执行,执行到cachedMapperMethod方法的时候,我打算看下这个方法的具体实现
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
第一次进入这个方法,mapperMethod必为空,所以执行new MapperMethod构造方法,构造出的mapperMethod对象包含一个sqlCommand对象和methodSignature对象,分别代表的含义是sql命令和方法签名
可以看到这里的命令类型是select,接下来断点进入mapperMethod的excute方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//我们的方法会走到这里
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//我们的方法是selectOne方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
mybatis单独使用时的sqlSession会DefaultSqlSession,在整合spring之后被sqlSessionTemplate所替换,执行selectOne方法,进入sqlSessionTemplate的selectOne方法
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy. selectOne(statement, parameter);
}
再一次进入代理对象的selectOne方法
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//被代理的DefaultSqlSession的selectOne方法在这里被执行
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
//每次都会关闭sqlSession,这就是mybatis整合spring后一级缓存会失效的原因
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
通过本次实验,我们知道了spring整合mybatis之后出现的一级缓存失效的情况以及失效的原因,多谢大家的支持,希望大家多多指点。