Mybatis使用中,同一个事物里,select查询不出之前insert的数据

Posted N!CE波

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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"

 

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

 

以上是关于Mybatis使用中,同一个事物里,select查询不出之前insert的数据的主要内容,如果未能解决你的问题,请参考以下文章

mybatisplus selectById 查不到结果

mybatis多表查询

mybatis----增删改查

MyBatis的增删改查操作

MyBatis学习SQL语句映射文件增删改查参数缓存

如何用mybatis框架进行数据库的增删改查