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

猜测:第二次查询走了Mybatis缓存

疑问:那为什么会走缓存呢?

1.service方法

  1. @Override
  2. @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
  3. public void test() {
  4. //1.第一次查询
  5. List<Integer> studentIdListByCid = tCourseStudentDao.findStudentIdListByCid(1);
  6. System.out.println(studentIdListByCid);
  7.  
  8. //2.插入一条新数据
  9. TCourseStudent tCourseStudent = new TCourseStudent();
  10. tCourseStudent.setCourseId(1);
  11. tCourseStudent.setStudentId(1);
  12. List list = new ArrayList();
  13. list.add(tCourseStudent);
  14. tCourseStudentDao.batchInsert(list);
  15.  
  16. //第二次查询
  17. List<Integer> studentIdListByCid2 = tCourseStudentDao.findStudentIdListByCid(1);
  18. System.out.println(studentIdListByCid2);
  19. }

2.dao方法

  1. @SelectProvider(type = TCourseStudentDaoSqlProvider.class, method = "batchInsert")
  2. void batchInsert(@Param("tCourseStudents") List<TCourseStudent> tCourseStudents);

二、解决方法

是因为dao的方法注解使用错了

将@SelectProvider换成@InsertProvider就可以

三、源码解析

1.执行batchInsert时,会调用MapperProxy的invoke方法,该方法中会构件MapperMethod对象,真正用来执行sql的

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2.  
  3. private static final long serialVersionUID = -6424540398559729838L;
  4. private final SqlSession sqlSession;
  5. private final Class<T> mapperInterface;
  6. private final Map<Method, MapperMethod> methodCache;
  7.  
  8. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  9. this.sqlSession = sqlSession;
  10. this.mapperInterface = mapperInterface;
  11. this.methodCache = methodCache;
  12. }
  13.  
  14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  15. if (Object.class.equals(method.getDeclaringClass())) {
  16. return method.invoke(this, args);
  17. }
  18. final MapperMethod mapperMethod = cachedMapperMethod(method);
  19. return mapperMethod.execute(sqlSession, args);
  20. }
  21.  
  22. private MapperMethod cachedMapperMethod(Method method) {
  23. MapperMethod mapperMethod = methodCache.get(method);
  24. if (mapperMethod == null) {
  25. mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  26. methodCache.put(method, mapperMethod);
  27. }
  28. return mapperMethod;
  29. }
  30.  
  31. }

1)当使用SelectProvider时,

构件MapperMethod时,type是"select"

2)当使用InsertProvider时,

构件MapperMethod时,type是"insert"

2.构件完MapperMethod后,会调用 mapperMethod.execute(sqlSession, args);然后根据SqlCommandType选择执行不同的sql方法

  1. public class MapperMethod {
  2.  
  3. private final SqlCommand command;
  4. private final MethodSignature method;
  5.  
  6. public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  7. this.command = new SqlCommand(config, mapperInterface, method);
  8. this.method = new MethodSignature(config, method);
  9. }
  10.  
  11. public Object execute(SqlSession sqlSession, Object[] args) {
  12. Object result;
  13. if (SqlCommandType.INSERT == command.getType()) {
  14. Object param = method.convertArgsToSqlCommandParam(args);
  15. result = rowCountResult(sqlSession.insert(command.getName(), param));
  16. } else if (SqlCommandType.UPDATE == command.getType()) {
  17. Object param = method.convertArgsToSqlCommandParam(args);
  18. result = rowCountResult(sqlSession.update(command.getName(), param));
  19. } else if (SqlCommandType.DELETE == command.getType()) {
  20. Object param = method.convertArgsToSqlCommandParam(args);
  21. result = rowCountResult(sqlSession.delete(command.getName(), param));
  22. } else if (SqlCommandType.SELECT == command.getType()) {
  23. if (method.returnsVoid() && method.hasResultHandler()) {
  24. executeWithResultHandler(sqlSession, args);
  25. result = null;
  26. } else if (method.returnsMany()) {
  27. result = executeForMany(sqlSession, args);
  28. } else if (method.returnsMap()) {
  29. result = executeForMap(sqlSession, args);
  30. } else {
  31. Object param = method.convertArgsToSqlCommandParam(args);
  32. result = sqlSession.selectOne(command.getName(), param);
  33. }
  34. } else {
  35. throw new BindingException("Unknown execution method for: " + command.getName());
  36. }
  37. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  38. throw new BindingException("Mapper method '" + command.getName()
  39. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  40. }
  41. return result;
  42. }
  43.  
  44. ....
  45. }

3.根据不同SqlCommandType执行不同的逻辑

1)当SqlCommandType为SqlCommandType.SELECT时,只是简单的执行查询逻辑,当前sql会执行selectOne方法

  1. if (SqlCommandType.SELECT == command.getType()) {
  2. if (method.returnsVoid() && method.hasResultHandler()) {
  3. executeWithResultHandler(sqlSession, args);
  4. result = null;
  5. } else if (method.returnsMany()) {
  6. result = executeForMany(sqlSession, args);
  7. } else if (method.returnsMap()) {
  8. result = executeForMap(sqlSession, args);
  9. } else {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = sqlSession.selectOne(command.getName(), param);
  12. }
  13. }
  1. sqlSession.selectOne===>然后执行
  1. SqlSessionInterceptor.invoke()====》然后执行
  2.  
  3. DefaultSqlSession.selectOne(),我们发现到这一步只是简单的执行以下我们的插入sql,并没有清除Mybatis缓存的逻辑
  1. public class SqlSessionTemplate implements SqlSession {
  2.  
  3. ...
  4. private class SqlSessionInterceptor implements InvocationHandler {
  5. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  6. final SqlSession sqlSession = getSqlSession(
  7. SqlSessionTemplate.this.sqlSessionFactory,
  8. SqlSessionTemplate.this.executorType,
  9. SqlSessionTemplate.this.exceptionTranslator);
  10. try {
  11. Object result = method.invoke(sqlSession, args);
  12. if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  13. // force commit even on non-dirty sessions because some databases require
  14. // a commit/rollback before calling close()
  15. sqlSession.commit(true);
  16. }
  17. return result;
  18. } catch (Throwable t) {
  19. Throwable unwrapped = unwrapThrowable(t);
  20. if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  21. Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
  22. if (translated != null) {
  23. unwrapped = translated;
  24. }
  25. }
  26. throw unwrapped;
  27. } finally {
  28. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  29. }
  30. }
  31. }
  32. }
  1. public class DefaultSqlSession implements SqlSession {
  2. public <T> T selectOne(String statement, Object parameter) {
  3. // Popular vote was to return null on 0 results and throw exception on too many.
  4. List<T> list = this.<T>selectList(statement, parameter);
  5. if (list.size() == 1) {
  6. return list.get(0);
  7. } else if (list.size() > 1) {
  8. throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  9. } else {
  10. return null;
  11. }
  12. }
  13. }

2)当SqlCommandType为SqlCommandType.INSERT时,执行sqlsession.insert()方法,并最终走BaseExecutor.update方法,该方法会清除缓存

除了SqlCommandType.INSERT,SqlCommandType.UPDATE,SqlCommandType.DELETE都会走BaseExecutor.update方法,所有会自动将Mybatis缓存清空,防止查询不到最新的数据

  1. if (SqlCommandType.INSERT == command.getType()) {
  2. Object param = method.convertArgsToSqlCommandParam(args);
  3. result = rowCountResult(sqlSession.insert(command.getName(), param));
  4. }
  1. public class SqlSessionTemplate implements SqlSession {
  2. 。。。
  3. public int insert(String statement, Object parameter) {
  4. return this.sqlSessionProxy.insert(statement, parameter);
  5. }
  1. 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);
    }
    }
    }
  1. }
  1. public class DefaultSqlSession implements SqlSession {
  2.  
  3. public int insert(String statement, Object parameter) {
  4. return update(statement, parameter);
  5. }
  1. 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();
    }
    }
  1. }
  1. public class Plugin implements InvocationHandler {
  2.  
  3. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  4. try {
  5. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  6. if (methods != null && methods.contains(method)) {
  7. return interceptor.intercept(new Invocation(target, method, args));
  8. }
  9. return method.invoke(target, args);
  10. } catch (Exception e) {
  11. throw ExceptionUtil.unwrapThrowable(e);
  12. }
  13. }
  14.  
  15. }
  1. public class CachingExecutor implements Executor {
  2.  
  3. public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  1. flushCacheIfRequired(ms); //不是这一步清除的缓存, Cache cache = ms.getCache();cache为null
  2. return delegate.update(ms, parameterObject);
  3. }
  4.  
  5. private void flushCacheIfRequired(MappedStatement ms) {
  6. Cache cache = ms.getCache();
  7. if (cache != null) {
  8. if (ms.isFlushCacheRequired()) {
  9. dirty = true; // issue #524. Disable using cached data for this session
  10. tcm.clear(cache);
  11. }
  12. }
  13. }
  14. }

 到这一步,clearLocalCache();才真正的清除掉本地缓存

  1.  
  1. public abstract class BaseExecutor implements Executor {
  2.  
  3. public int update(MappedStatement ms, Object parameter) throws SQLException {
  4. ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  5. if (closed) throw new ExecutorException("Executor was closed.");
  6. clearLocalCache();
  7. return doUpdate(ms, parameter);
  8. }
  9.  
  10. public void clearLocalCache() {
  11. if (!closed) {
  12. localCache.clear();
  13. localOutputParameterCache.clear();
  14. }
  15. }
  16. }

  1. public class SimpleExecutor extends BaseExecutor {
  2. public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  3. Statement stmt = null;
  4. try {
  5. Configuration configuration = ms.getConfiguration();
  6. StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
  7. stmt = prepareStatement(handler, ms.getStatementLog());
  8. return handler.update(stmt);
  9. } finally {
  10. closeStatement(stmt);
  11. }
  12. }
  13. }
  1. public class RoutingStatementHandler implements StatementHandler {
  2.  
  3. public int update(Statement statement) throws SQLException {
  4. return delegate.update(statement);
  5. }
  6. }
  1. public class PreparedStatementHandler extends BaseStatementHandler {
  2. public int update(Statement statement) throws SQLException {
  3. PreparedStatement ps = (PreparedStatement) statement;
  4. ps.execute();
  5. int rows = ps.getUpdateCount();
  6. Object parameterObject = boundSql.getParameterObject();
  7. KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  8. keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  9. return rows;
  10. }
  11. }

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

  1. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  2. vscode源码分析【九】窗口里的主要元素

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...

  3. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  4. 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  5. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  6. 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  8. 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. mybatis 学习四 源码分析 mybatis如何执行的一条sql

    总体三部分,创建sessionfactory,创建session,执行sql获取结果 1,创建sessionfactory      这里其实主要做的事情就是将xml的所有配置信息转换成一个Confi ...

随机推荐

  1. Regsvr32 在64位机器上的用法(转载)

    转载:http://blog.csdn.net/xuzhimin1991/article/details/65436864 regsvr32是windows上注册 OLE 控件(DLL 或 Activ ...

  2. 3.sql2008查询

    根据需要和条件,查看并显示结果集,如果需要,可将结果集生成数据表select:查什么,列筛选,可以用*代表全部列from:在哪个表中查,where:符合什么样的条件,行筛选select:       ...

  3. Oracle错误——SP2-0734: 未知的命令开头 "imp C##sin..." - 忽略了剩余的行。

    错误 在windows的DOS窗口下使用命令导入Oracle数据. 原因 进入sqlplus里是不能执行imp的(sqlplus不认识imp),imp 是个工具,应该在cmd的dos命令提示符下执行.

  4. [转] J2EE基础知识

    Servlet总结 阐述Servlet和CGI的区别? CGI的不足之处: Servlet的优点: Servlet接口中有哪些方法及Servlet生命周期探秘 get和post请求的区别 什么情况下调 ...

  5. C# 禁止任务管理器关闭

    http://www.cnblogs.com/luomingui/archive/2011/06/25/2090130.html 测试了好像没用的.不知道什么原因

  6. P4822 [BJWC2012]冻结

    思路 和p4568类似的分层图最短路 从上一层向下一层连边权/2的边即可 代码 #include <cstdio> #include <algorithm> #include ...

  7. [nginx] - 使用nginx实现反向代理,动静分离,负载均衡,session共享

    反向代理概念 先说正向代理,比如要访问youtube,但是不能直接访问,只能先找个FQ软件,通过FQ软件才能访问youtube. FQ软件就叫做正向代理.所谓的反向代理,指的是用户要访问youtube ...

  8. LightOJ 1268 Unlucky Strings(KMP+矩阵乘法+基础DP)

    题意 给出字符串的长度 \(n\) ,以及该字符串是由哪些小写字母组成,现给出一个坏串 \(S\) ,求存在多少种不同的字符串,使得其子串不含坏串. \(1 \leq n \leq 10^9\) \( ...

  9. 【Hadoop 分布式部署 八:分布式协作框架Zookeeper架构功能讲解 及本地模式安装部署和命令使用 】

    What  is  Zookeeper 是一个开源的分布式的,为分布式应用提供协作服务的Apache项目 提供一个简单的原语集合,以便与分布式应用可以在他之上构建更高层次的同步服务 设计非常简单易于编 ...

  10. Transaction

    SqlTransaction——事务详解 事务是将一系列操作作为一个单元执行,要么成功,要么失败,回滚到最初状态.在事务处理术语中,事务要么提交,要么中止.若要提交事务,所有参与者都必须保证对数据的任 ...