解析ソースMyBatisのシャーディング・対応のJDBC SQLステートメントは、詳細な処理を実行し(エンドテキストがフローチャートを有します)

マッパーは、MyBatisの中であるため、この記事で詳細に実行するプロセス全体MyBatisのSQL文、紙や記事は、いくつかの関連性を持っている、3つの記事の前のシリーズを読むことをお勧めします説明は、初期化プロセスMyBatisのマッパークラスの習得に注力します次のコードのようなエントリのSQL文を実行します。

@Service
public UserService implements IUserService {
 	@Autowired
    private UserMapper userMapper;
    public User findUser(Integer id) {
        return userMapper.find(id);
    }
}
复制代码

人気のMyBatisのは、SQL文を実行分析するための手段として、ソースコード、およびサブライブラリーのサブテーブルデータベースミドルウェアのシャーディング-JDBCの使用、そのバージョンシャーディング-jdbc1.4.1で、この記事の件名を入力し始めました。

このメソッドのソースコードの解析を容易にするために、MyBatisのレベルのコアクラスの最初の呼び出しのシーケンス図を与えています。

1、図のSQL実行シーケンス。

ここに画像を挿入説明

図2に示すように、ソースコード解析SQL実行処理

次は、彼らの視点からソースコードを分析します。

ヒント:この記事の終わりには、MyBatisのShardingjdbc文の実装の詳細なフローチャートが与えられます。(それをお見逃しなく)。

2.1 MapperProxy#呼び出し

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);   // @1
    return mapperMethod.execute(sqlSession, args);                                     // @2
  }
复制代码

1 @コード:MapperMethodオブジェクトを作成し、キャッシュ。

2 @コード:即ち、各方法は、最終mapperInterface MapperMethodの対応する一つに定義される、MapperMethodオブジェクトのメソッド呼び出しを実行します。

2.2 MapperMethod#実行

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;
  }
复制代码

この方法は、主executeForMany(SQLSESSION、引数)メソッドに、対応する論理本明細書で我々クエリ、トラックを実行し、他の操作を選択し、SQL、挿入、更新の種類に基づいています。

2.3 MapperMethod#executeForMany

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
复制代码

この方法は、比較的単純であり、そして最終的にSelectListのSQLSESSIONメソッドを呼び出すこともできます。

2.4 DefaultSqlSession#SelectListの

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);   // @1
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);   // @2
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
复制代码

1コード@:MappedStatementに対応する目的に応じてリソースの名前を取得し、この時点では、ステートメントは、例えばcom.demo.UserMapper.findUserリソースの名前です。セクションは、初期化を介して詳細に説明してきたときにMappedStatementオブジェクトを生成するように、説明は省略する。

コード@ 2:クエリーメソッド呼び出しの執行。実際には、最初にCachingExecutor#クエリメソッドに入り、ここで説明し、原因CachingExecutorキュータデリゲートプロパティのデフォルトはSimpleExecutorあり、それが最終的にSimpleExecutor#クエリ内に入ります。

次は、私たちは中SimpleExecutorの親クラスBaseExecutorのクエリメソッドを入力してください。

2.5 BaseExecutor#クエリ

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {   // @1
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;                                            // @2
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);                   // @3
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {                         // @4
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }
复制代码

コード@ 1:まず、メソッドのパラメータを教えて、これらのクラスは、MyBatisのの重要なクラスです。

  • MappedStatementのMSはMappedStatemnetオブジェクトは、マッパー方法で表す最も基本的なオブジェクトマップである、文をマッピングしました。
  • SQL文のパラメータオブジェクトのパラメータリスト。
  • RowBoundsのrowBoundsはパラメータの上限とサイズをページング、実際には、境界オブジェクトを並べます。
  • ResultHandler resultHandlerは、ハンドラの処理結果。
  • CacheKeyキーMyBatisの缓存キー
  • BoundSql boundSql SQLとパラメータのバインディング情報は、あなたがオブジェクトのマッピング・ファイル内のSQL文を取得することができます。

2 @コード:最初のキャッシュから取得し、MyBatisのは(SQLSESSIONが共用S)キャッシュ(SQLSESSION)と二次キャッシュをサポートしています。

3 @コード:データベースクエリからの結果、およびその後doQueryメソッドに進み、実際のクエリ操作を行います。

4 @コード:キャッシュは、文レベル、キャッシュを削除し、実施後の文である場合。

2.6 SimpleExecutor#doQuery

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);   // @1
      stmt = prepareStatement(handler, ms.getStatementLog());                                                                                                                   // @2
      return handler.<E>query(stmt, resultHandler);                                                                                                                                        // @3
    } finally {
      closeStatement(stmt);
    }
  }
复制代码

1 @コード:示すようStatementHandlerを作成し、(パート2に詳細に記載する)MyBatisのプラグ拡張機構が追加されます。

ここに画像を挿入説明
2 @コード:これはJDBCプロトコルの対象java.sql.StatementのあることStatementオブジェクト、ノートを作成します。

3 @コード:なステートメントオブジェクトを使用してSQL文を実行します。

次に、Statementオブジェクトの作成と実行処理、すなわち、詳細なトラッキングコードの分布と2 @ 3 @コードの詳細。

3、Statementオブジェクトの作成プロセス

3.1 java.sql.Connectionオブジェクトの作成

3.1.1 SimpleExecutor#のprepareStatement

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);  // @1
    stmt = handler.prepare(connection);                                  // @2
    handler.parameterize(stmt);                                               // @3
    return stmt;
}
复制代码

1 @コード:Statementオブジェクトを作成し、3つの段階に分けてjava.sql.Connectionオブジェクトを作成します。

2 @コード:Connectionオブジェクトを使用しなステートメントオブジェクトを作成します。

コード@ 3:追加の処理のためのステートメント、特定のパラメータのprepareStatement(ParameterHandler)。

3.1.2 SimpleExecutor#のgetConnection

次のようにgetConnectionメソッドは、上記に示すフローチャートに従って、第org.mybatis.spring.transaction.SpringManagedTransactionに、ばねJDBCフレームを介して、DataSourceUtilsが接続コードを取得し使用することです。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {  
		Assert.notNull(dataSource, "No DataSource specified");
		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); 
		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				conHolder.setConnection(dataSource.getConnection());
			}
			return conHolder.getConnection();
		}
		// Else we either got no holder or an empty thread-bound holder here.

		logger.debug("Fetching JDBC Connection from DataSource");
		Connection con = dataSource.getConnection();      // @1

        // 这里省略与事务处理相关的代码
		return con;
	}
复制代码

コード@ 1:データソースを介して接続を取得し、ここでのDataSourceが「誰が」やるということですか?私たちは、プロジェクトの設定を見てみましょう。

ここに画像を挿入説明
ここに画像を挿入説明

したがって、最終的なdataSouce.getConnectionは接続がSpringShardingDataSourceから取得し、接続を取得しました。

com.dangdang.ddframe.rdb.sharding.jdbc.ShardingDataSource#getConnection
public ShardingConnection getConnection() throws SQLException {
        MetricsContext.init(shardingProperties);
        return new ShardingConnection(shardingContext);
}
复制代码

次のように返された結果は以下のとおりです。

ここに画像を挿入説明
注:ここでの唯一のサブライブラリーサブテーブルのコンテキストを含んShardingConnectionオブジェクトを返すが、この時間はない特定のサブライブラリーは、(データソースを切り替え)動作を行います。

取得プロセスの後に接続が明確な、私たちは創造Statemnetオブジェクトを見続けています。

3.2 java.sql.Statementのオブジェクト作成

stmt = prepareStatement(handler, ms.getStatementLog());            
复制代码

声明上記のコールチェーン:RoutingStatementHandler - 「BaseStatementHandler

3.2.1 BaseStatementHandler#準備

public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);    // @1
      setStatementTimeout(statement);                         // @2
      setFetchSize(statement);                                      // @3
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
复制代码

1 @コード:PreparedStatementHandler#instantiateStatement方法:オブジェクトは、Statement Connectionオブジェクト(ここShardingConnectionがある)、デフォルトの実装クラスを作成することです。

2 @コード:タイムアウト時間ステートメントを設定します。

コード@ 3:セットのfetchSize。

3.2.2 PreparedStatementHandler#instantiateStatement

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }
复制代码

接続がShardingConnectionあるので、実際には、比較的単純な、Statementオブジェクトを作成するには、その対応するprepareStatementメソッドに見えます。

3.2.2 ShardingConnection#のprepareStatement

public PreparedStatement prepareStatement(final String sql) throws SQLException {   // sql,为配置在mybatis xml文件中的sql语句
        return new ShardingPreparedStatement(this, sql);
}
ShardingPreparedStatement(final ShardingConnection shardingConnection, 
            final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
        super(shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability);
        preparedSQLRouter = shardingConnection.getShardingContext().getSqlRouteEngine().prepareSQL(sql);
}
复制代码

建物ShardingPreparedStatementオブジェクトは、SQL文の解析SQLパーサオブジェクトルートに基づいて作成されますが、今回は、関連するルート計算を実装しません場合は、PreparedStatementオブジェクトが作成され、彼らは、実行SQLの流れを入力し始めます。

4、SQL実行プロセス

次に、我々は、SQL文を実行し、ステップ3 SimpleExecutor#doQueryメソッドを見続けます。

handler.<E>query(stmt, resultHandler)。
复制代码

最初RoutingStatementHandlerにこのクラスを入力し、ルートMyBatisのレベル(主に文のタイプに基づいて)

ここに画像を挿入説明
そして、中PreparedStatementHandlerの#クエリに進みます。

4.1 PreparedStatementHandler#クエリ

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();  // @1
    return resultSetHandler.<E> handleResultSets(ps);  // @2
}
复制代码

1 @コード:ShardingPreparedStatement:PreparedStatementのメソッドを実行し、本実施形態は、サブライブラリシャーディング-JDBCサブテーブルを使用することであるので、呼び出しは、現時点で具体化される、と呼ばれています。

処理結果:2コード@。

その後、我々は実行され、治療の結果をフォローアップすることでした。

4.2 ShardingPreparedStatement#実行

public boolean execute() throws SQLException {
    try {
        return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).execute(); // @1
    } finally {
        clearRouteContext();
    }
}
复制代码

ここでは無限の謎、次のようにキーポイントは次のとおりです。1)PreparedStatementExecutorを作成するには、その2つのコアの引数をオブジェクト:

  • ExecutorEngine executorEngine:shardingjdbc実行エンジン。
  • コレクションpreparedStatemenWrappers、各セットはPreparedStatementクラスをパッケージ化されたセット、どのようにこのコレクションを来る<PreparedStatementExecutorWrapper>?

2)preparedStatemenWrappers routeSQLは方法によって製造されます。

3)最終的なコールPreparedStatementExecutor方法は、実行するために実行されます。

そして、それぞれrouteSQLを見て、メソッドを実行します。

4.3 ShardingPreparedStatement#routeSQL

private List<PreparedStatementExecutorWrapper> routeSQL() throws SQLException {
        List<PreparedStatementExecutorWrapper> result = new ArrayList<>();
        SQLRouteResult sqlRouteResult = preparedSQLRouter.route(getParameters());   // @1
        MergeContext mergeContext = sqlRouteResult.getMergeContext();                      
        setMergeContext(mergeContext);
        setGeneratedKeyContext(sqlRouteResult.getGeneratedKeyContext());
        for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) {                      // @2          
            PreparedStatement preparedStatement = (PreparedStatement) getStatement(getShardingConnection().getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql());     // @3
            replayMethodsInvocation(preparedStatement);
            getParameters().replayMethodsInvocation(preparedStatement);
            result.add(wrap(preparedStatement, each));
        }
        return result;
}
复制代码

コード@ 1:SQLパラメータは、経路計算によると、紙が一時的にその特定の実装の詳細に焦点を当てるためには、これらは、具体的結果で直感的な外観に、ここで、時に特定の分析シャーディング-JDBCを説明します:

コード@ 2、@ 3:サブライブラリーサブテーブルの結果が横断し、[接続、基礎となるデータソースを作成するために使用するPreparedStatementオブジェクトを作成しています。

routeSQLは一時的に私たちは、ルートの結果に基づいて、ここから知っている具体的な対応の接続を作成するために、基礎となるデータソースを使用して、PreparedStatementは、ここではオブジェクト、これについて話しました。

4.4 PreparedStatementExecutor#実行

public boolean execute() {
    Context context = MetricsContext.start("ShardingPreparedStatement-execute");
    eventPostman.postExecutionEvents();
    final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
    final Map<String, Object> dataMap = ExecutorDataMap.getDataMap();
    try {
        if (1 == preparedStatementExecutorWrappers.size()) {     // @1
            PreparedStatementExecutorWrapper preparedStatementExecutorWrapper = preparedStatementExecutorWrappers.iterator().next();
            return executeInternal(preparedStatementExecutorWrapper, isExceptionThrown, dataMap);
        }
        List<Boolean> result = executorEngine.execute(preparedStatementExecutorWrappers, new ExecuteUnit<PreparedStatementExecutorWrapper, Boolean>() {    // @2
        
            @Override
            public Boolean execute(final PreparedStatementExecutorWrapper input) throws Exception {
                synchronized (input.getPreparedStatement().getConnection()) {
                    return executeInternal(input, isExceptionThrown, dataMap);
                }
            }
        });
        return (null == result || result.isEmpty()) ? false : result.get(0);
    } finally {
        MetricsContext.stop(context);
    }
 }
复制代码

コード@ 1:ルートが1計算されている場合は、同期が行われます。

2 @コード:計算ルートの場合は複数の非同期実行スレッドプールが使用されているがあります。

もう一つの問題は、どのように結果を返すメソッドの実行を実行するのPreparedStatement#で、それですか?特に、非同期実行で。

実際には、既に述べました:

4.4 DefaultResultSetHandler#handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);         // @1
    //省略部分代码,完整代码可以查看DefaultResultSetHandler方法。
    return collapseSingleResultList(multipleResults);
  }

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
    ResultSet rs = stmt.getResultSet();              // @2
    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -1) {
          // no more results. Must be no resultset
          break;
        }
      }
    }
    return rs != null ? new ResultSetWrapper(rs, configuration) : null;
  }
复制代码

私たちは、キーコードを見てのように、次のされます。コード@ 1:Callステートメントの#のgetResultSet()メソッド、shardingJdbc場合、ShardingStatement#のgetResultSet()が呼び出されます、そしてここでは詳細に説明されていない結果セットとサブライブラリーサブテーブルの契約をマージしますこのセクションはshardingjdbc欄に記載されている詳細に分析します。

コード@ 2:一般的な結果セットの場合は書面でJDBCステートメントは、ここであまり紹介ではありません。

MyBatisのshardingjdbcのSQL​​実行処理は、上述した方法の理解を容易にするために、ここで説明し、SQLの実行のフローチャートが与えられます。

ここに画像を挿入説明

MyBatisのシャーディング・対応のJDBC SQLの実行プロセスは、MyBatisのからの数値が明確に詳細に説明解体メカニズムを、見ることができ、ここで説明します。


著者は紹介:「RocketMQ技術インサイダー、」著者は、公共の番号維持:ミドルウェア興味円、主にJavaのソースコードの読み取りの収集、JUC(Javaの契約)、ネッティー、ElasticJob、Mycat、公表ダボ、RocketMQ、mybaits 他のソースを。

ここに画像を挿入説明

おすすめ

転載: juejin.im/post/5dd3de93f265da0bf2112634