zoukankan      html  css  js  c++  java
  • 【源码分析】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;
      }
    }
  • 相关阅读:
    CodeForces 404C Ivan and Powers of Two
    CodeForces 433C Ryouko's Memory Note-暴力
    if not
    python3的print函数
    交叉熵
    tensorflow一个很好的博客
    关于第几维的问题
    更新软件
    tensorflow训练代码
    tensorflow的一些函数
  • 原文地址:https://www.cnblogs.com/756623607-zhang/p/10291704.html
Copyright © 2011-2022 走看看