Mybatis-Plus的使用(五)——源碼理解

原文鏈接:橙子&栗子&世界——Mybatis-Plus的使用(五)——源碼理解

在說Mybatis-Plus(後面簡稱MP)之前,我們先來了解了解Mybatis的工作流程。

Mybatis的執行流程的分析

先來認識一些Mybatis的主要的類和接口:

  • Configuration:將mybatis配置文件中的信息保存到該類中
  • SqlSessionFactory:解析Configuration類中的配置信息,獲取SqlSession
  • SqlSession:負責和數據庫交互,完成增刪改查
  • Executor:mybatis的調度核心,負責SQL的生成
  • StatementHandler:封裝了JDBC的statement操作
  • ParameterHandler:負責完成JavaType到jdbcType的轉換
  • ResultSetHandler:負責完成結果集到Java Bean的轉換
  • MappedStatement:代表一個select|update|insert|delete元素
  • SqlSource:根據傳入的ParamterObject生成SQL
  • BoundSql:包含SQL和參數信息

獲取SqlSession的流程圖:
mybatis之SqlSession .png

獲取SqlSessionFactory

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

通過XMLConfigBuilder解析配置文件,解析的配置相關信息都會封裝爲一個Configuration對象,build(parser.parse())這一步就是獲取DefaultSqlSessionFactory

獲取SqlSession

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

configuration類裏包含了Mybatis的相關配置信息,Environment包含了數據源和事務的配置,Executor是mybatis的調度核心(表面上是sqlSession完成與數據庫的增刪改查,實際上是通過Executor執行,因爲它是對於Statement的封裝)(Statement則是java操作數據庫的重要對象)。上面代碼獲取了一個包含configuration和executor的DefaultSqlSession對象。

sqlSession調用增刪改查

查看DefaultSqlSession源碼可以看出,它的增刪改查方法,最終都會走到其中三個方法上,如下:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
  @Override
  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();
    }
  }

而最終的SQL執行都是使用Executor執行,Executor接口有一個抽象實現類BaseExecutor,而該類的query()update()方法,最終都會走到其三個子類(SimpleExecutor、ReuseExecutor、BatchExecutor)上面,這三個子類也就是具體的實現:

  • SimpleExecutor:是Mybatis執行Mapper語句默認執行的Executor,從名稱可以看出就是簡單的執行器,執行每個語句都會創建一個新的預處理語句;
  • ReuseExecutor:指可以複用的執行器,執行每個語句會去檢查是否有緩存的預處理語句(實際上是指緩存的Statement對象),有的話就直接使用,沒有的會新建
  • BatchExecutor:批量處理的執行器,主要是用於做批量的更新操作的,其底層會調用Statement的executeBatch()方法實現批量操作

Mapper中的方法調用

首先,我們通過@Autowired來對mapper的進行注入,Spring掃描dao層,爲每一個mapper接口創建一個MapperFactoryBean,

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
 
  private Class<T> mapperInterface;
 
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
 
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
 
}

mapperInterface就是dao的class對象,因爲實現了FactoryBean接口,因此通過@Autowired獲取對象時,實際是調用getObject方法獲取對象,也就是sqlSession.getMapper();其流程如下:
mybatis之getMapper.png
我們剝開一層又一層,來到了MapperRegistry,這裏面getMapper實際上返回的是MapperProxyFactory,我們再來看看它:

public class MapperProxyFactory<T> {
  //我們的Dao接口的class對象
  private final Class<T> mapperInterface;
 
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
 
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
 
}

可以看出,是通過了動態代理創建mapper接口的代理類對象,而對接口所有方法的調用,最後都會回到調用mapperProxy的invoke方法上(這就是JDK動態代理)。

什麼是JDK的動態代理

我們去看看mapperProxy對象的invoke方法,我們去看看mapperProxy對象的invoke方法:


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;
 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
	  //判斷你調用的是否是已實現的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
 
}

if判斷我們調用的方法是否是對象中的,我們調用的都是接口的方法,所以直接走mapperMethod.execute()。mapperMethod標識我們調用接口中的那個方法

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, mapperInterface, method);
  }
 
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        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 if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }
}

這樣可以看出了,最終還是落到了sqlSession的update、select等;而這其實我們已經知道了,最後SqlSession其實是交給Statement執行SQL命令;
那問題又來了,Statement執行的sql是怎拼接出來的呢,這個我之後再去詳細瞭解,(目前瞭解到時把Mapper.xml解析,然後把#{}的字符替換成?,最後包裝成預表達式供給PrepareStatement執行)

Mybatis-Plus與Mybatis的異同點

前面我們大概瞭解到了Mybatis的工作原理,那MP是怎樣在他之上只做增強不做改變的呢?
MP去Mybatis的區別在哪兒呢,MP繼承了MapperRegistry這個類然後重寫了addMapper方法

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            // TODO 如果之前注入 直接返回
            return;
            // throw new BindingException("Type " + type +
            // " is already known to the MybatisPlusMapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
            knownMappers.put(type, new MapperProxyFactory<>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            // TODO 自定義無 XML 注入
            MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

將原方法中的MapperAnnotationBuilder替換成了自家的MybatisMapperAnnotationBuilder,在這裏特別說明一下,mp爲了不更改mybatis原有的邏輯,會用繼承或者直接粗暴地將其複製過來,然後在原有的類名上加上前綴Mybatis

然後關鍵是parser.parse()這個方法:

@Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            assistant.setCurrentNamespace(type.getName());
            parseCache();
            parseCacheRef();
            Method[] methods = type.getMethods();
            // TODO 注入 CURD 動態 SQL (應該在註解之前注入)
            if (GlobalConfigUtils.getSuperMapperClass(configuration).isAssignableFrom(type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
            for (Method method : methods) {
                try {
                    // issue #237
                    if (!method.isBridge()) {
                        parseStatement(method);
                    }
                } catch (IncompleteElementException e) {
                    configuration.addIncompleteMethod(new MethodResolver(this, method));
                }
            }
        }
        parsePendingMethods();
    }

sql注入器就是從這個方法裏面添加上去的,首先判斷Mapper是否是BaseMapper的超類或者超接口,BaseMapper是mp的基礎Mapper,裏面定義了很多默認的基礎方法,意味着我們一旦使用上mp,通過sql注入器,很多基礎的數據庫單表操作都可以直接繼承BaseMapper實現,如果是自定義的方法,sql注入器也會解析然後注入自定義的方法(這部分以後進一步瞭解後會補充)。

下面我們來看一個另一實例,ServiceImpl的saveBatch()方法(Service層調用的批量插入):

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        try (SqlSession batchSqlSession = sqlSessionBatch()) {
            int i = 0;
            for (T anEntityList : entityList) {
                batchSqlSession.insert(sqlStatement, anEntityList);
                if (i >= 1 && i % batchSize == 0) {
                    batchSqlSession.flushStatements();
                }
                i++;
            }
            batchSqlSession.flushStatements();
        }
        return true;
    }

sqlSessionBatch()是用來獲取sqlSession,並且指定Executor類型爲BATCH,然後循環batchSqlSession.insert(sqlStatement, anEntityList),發現實際上是調用的BatchExecutor的doUpdate()方法,

	@Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
     handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }

循環添加多個Statement,但是沒有但是還沒有提交,一直要到batchSqlSession.flushStatements()實際上是調用BatchExecutor的doFlushStatements(),這裏纔是真正的提交,把所有的Statement批量提交了

	@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    try {
      List<BatchResult> results = new ArrayList<BatchResult>();
      if (isRollback) {
        return Collections.emptyList();
      }
      for (int i = 0, n = statementList.size(); i < n; i++) {
        Statement stmt = statementList.get(i);
        applyTransactionTimeout(stmt);
        BatchResult batchResult = batchResultList.get(i);
        try {
          batchResult.setUpdateCounts(stmt.executeBatch());
          MappedStatement ms = batchResult.getMappedStatement();
          List<Object> parameterObjects = batchResult.getParameterObjects();
          KeyGenerator keyGenerator = ms.getKeyGenerator();
          if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
            Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
            jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
          } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
            for (Object parameter : parameterObjects) {
              keyGenerator.processAfter(this, ms, stmt, parameter);
            }
          }
          // Close statement to close cursor #1109
          closeStatement(stmt);
        } catch (BatchUpdateException e) {
          StringBuilder message = new StringBuilder();
          message.append(batchResult.getMappedStatement().getId())
              .append(" (batch index #")
              .append(i + 1)
              .append(")")
              .append(" failed.");
          if (i > 0) {
            message.append(" ")
                .append(i)
                .append(" prior sub executor(s) completed successfully, but will be rolled back.");
          }
          throw new BatchExecutorException(message.toString(), e, results, batchResult);
        }
        results.add(batchResult);
      }
      return results;
    } finally {
      for (Statement stmt : statementList) {
        closeStatement(stmt);
      }
      currentSql = null;
      statementList.clear();
      batchResultList.clear();
    }
  }

之前我有些疑惑,爲什麼要添加多個Statement,然後循環執行stmt.executeBatch()來提交,而MP的saveBatch()只是做了一個表的批量提交,爲什麼不循環statement.addBatch然後在statement.executeBatch()一次就提交了,後來我猜想可能是爲了無侵入吧,不在原來的Mybatis上修改太多的東西,只做增強,不做改變

以上內容來源於

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章