【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据

时间:2023-03-09 17:43:59
【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据

一、问题场景模拟
问题:第二次查询和第一次查询结果一模一样,没有查询出我新插入的数据

猜测:第二次查询走了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"

【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据

2)当使用InsertProvider时,

构件MapperMethod时,type是"insert"

【源码分析】Mybatis使用中,同一个事物里,select查询不出之前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();
}
}
}

【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据

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;
}
}