一、問題場景模擬
問題:第二次查詢和第一次查詢結果一模一樣,沒有查詢出我新插入的數據
猜測:第二次查詢走了Mybatis緩存
疑問:那為什么會走緩存呢?
1.service方法
@Override @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED) public void test() { //1.第一次查詢 List<Integer> studentIdListByCid = tCourseStudentDao.findStudentIdListByCid(1); System.out.println(studentIdListByCid); //2.插入一條新數據 TCourseStudent tCourseStudent = new TCourseStudent(); tCourseStudent.setCourseId(1); tCourseStudent.setStudentId(1); List list = new ArrayList(); list.add(tCourseStudent); tCourseStudentDao.batchInsert(list); //第二次查詢 List<Integer> studentIdListByCid2 = tCourseStudentDao.findStudentIdListByCid(1); System.out.println(studentIdListByCid2); }
2.dao方法
@SelectProvider(type = TCourseStudentDaoSqlProvider.class, method = "batchInsert") void batchInsert(@Param("tCourseStudents") List<TCourseStudent> tCourseStudents);
二、解決方法
是因為dao的方法注解使用錯了
將@SelectProvider換成@InsertProvider就可以
三、源碼解析
1.執行batchInsert時,會調用MapperProxy的invoke方法,該方法中會構件MapperMethod對象,真正用來執行sql的
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 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; } }
1)當使用SelectProvider時,
構件MapperMethod時,type是"select"
2)當使用InsertProvider時,
構件MapperMethod時,type是"insert"
2.構件完MapperMethod后,會調用 mapperMethod.execute(sqlSession, args);然后根據SqlCommandType選擇執行不同的sql方法
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { 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 { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { 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; } .... }
3.根據不同SqlCommandType執行不同的邏輯
1)當SqlCommandType為SqlCommandType.SELECT時,只是簡單的執行查詢邏輯,當前sql會執行selectOne方法
if (SqlCommandType.SELECT == command.getType()) { 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 { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } }
sqlSession.selectOne===>然后執行
SqlSessionInterceptor.invoke()====》然后執行
DefaultSqlSession.selectOne(),我們發現到這一步只是簡單的執行以下我們的插入sql,並沒有清除Mybatis緩存的邏輯
public class SqlSessionTemplate implements SqlSession {
... private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { 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) { Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
public class DefaultSqlSession implements SqlSession { public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } }
2)當SqlCommandType為SqlCommandType.INSERT時,執行sqlsession.insert()方法,並最終走BaseExecutor.update方法,該方法會清除緩存
除了SqlCommandType.INSERT,SqlCommandType.UPDATE,SqlCommandType.DELETE都會走BaseExecutor.update方法,所有會自動將Mybatis緩存清空,防止查詢不到最新的數據
if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); }
public class SqlSessionTemplate implements SqlSession { 。。。 public int insert(String statement, Object parameter) { return this.sqlSessionProxy.insert(statement, parameter); }
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
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) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
public class DefaultSqlSession implements SqlSession { public int insert(String statement, Object parameter) { return update(statement, parameter); }
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class Plugin implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
public class CachingExecutor implements Executor { public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms); //不是這一步清除的緩存, Cache cache = ms.getCache();cache為null return delegate.update(ms, parameterObject); } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null) { if (ms.isFlushCacheRequired()) { dirty = true; // issue #524. Disable using cached data for this session tcm.clear(cache); } } } }
到這一步,clearLocalCache();才真正的清除掉本地緩存
public abstract class BaseExecutor implements Executor { 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); } public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } }
public class SimpleExecutor extends BaseExecutor { public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } }
public class RoutingStatementHandler implements StatementHandler { public int update(Statement statement) throws SQLException { return delegate.update(statement); } }
public class PreparedStatementHandler extends BaseStatementHandler { public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); int rows = ps.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; } }