2018-06-07 Mybatis MYSQL読み書き分離マスターキー取得の問題に遭遇

5460 ワード

に質問
条件:MYSQLデータベースは読み書き分離エージェントを使用し、例えばアリクラウドのRDS+プライマリスレーブ分離エージェントを使用して簡単なInsertに対してプライマリキーIDが取得されず、getId()は0を返す.
以下は一般的な書き方です.
 
    
      SELECT LAST_INSERT_ID()
    
    insert into dalao_test (name, gender, create_time, 
      update_time, status)
    values (#{name,jdbcType=VARCHAR}, #{gender,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, 
      #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER})
  

次は
  
    insert into dalao_test (name, gender, create_time, 
      update_time, status)
    values (#{name,jdbcType=VARCHAR}, #{gender,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, 
      #{updateTime,jdbcType=TIMESTAMP}, #{status,jdbcType=INTEGER})
  

区別する
どちらもプライマリ・キーを取得する書き方です.1つ目は、SelectKeysを使用する場合、MybatisはSelectKeyGenerator,INSERTを使用した後、クエリー文を1回多く送信してプライマリ・キー値を取得し、上記読み書き分離がエージェントされている場合、正しいプライマリ・キーが得られないことです.
2つ目は、MapperXMLがuseGeneratedKeys=trueを使用してSelectKeyノードを書かず、Mybatisの構成でuseGeneratedKeysをオンにすると、MybatisはJdbc 3 KeyGeneratorを使用します(以下のparseStatementNodeの注釈を参照できます)ただし、プライマリ・キーがidでない場合は定義する必要があります
keyColumn="anotherId" keyProperty="anotherId" 

このKeyGeneratorを使用するメリットは、1回のINSERT文内でresultSetを介して生成されたプライマリ・キー値を直接取得し、プライマリ・ライブラリを指定することなく、読み書き分離エージェントを設定したデータベースをサポートすることです.
げんり
//org.apache.ibatis.builder.xml.XMLStatementBuilder
public void parseStatementNode() {
//      
//       SelectKey   ,   KeyGenerator
      // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

//       KeyGenerator  ,       useGeneratedKeys。
//  XML  selectKey,        useGeneratedKeys     Jdbc3KeyGenerator。
 if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

//    selectKey     selectKeyGenerator。
 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

}


最後にINSERTを実行した後、processAfterを呼び出す.
public interface KeyGenerator {

  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

具体的なJdbc 3 KeyGeneratorとselectKeyGeneratorの違いは、ソースコードを表示できます.
ソリューション
前述したように、MybatisがINSERTまたはUPDATEのためにJdbc 3 KeyGeneratorを使用する方法は、SelectKeyを書かずにuseGeneratedKeys=trueをオンにすることである.


もう1つの一時的なシナリオは、ブロックでSelectKeyの文を強制的にマスターライブラリにアクセスさせることです.
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MyBatisPlugin implements Interceptor{
    private static final Logger logger = LoggerFactory.getLogger(MyBatisPlugin.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
        Object objects = (Object)invocation.getArgs()[1];
        BoundSql boundSql = mappedStatement.getBoundSql(objects);

        //               :https://help.aliyun.com/knowledge_detail/52221.html
        if ((boundSql.getSql().contains("LAST_INSERT_ID") || StringUtils.contains(mappedStatement.getId(), "selectKey")) && !boundSql.getSql().contains("FORCE_MASTER")) {
            SqlSource sqlSource = mappedStatement.getSqlSource();
            if (sqlSource instanceof RawSqlSource) {
                Class extends RawSqlSource> aClass = ((RawSqlSource) sqlSource).getClass();
                Field sqlField = aClass.getDeclaredField("sqlSource");
                sqlField.setAccessible(true);
                Object staticSqlSource = sqlField.get(sqlSource);
                if (staticSqlSource instanceof StaticSqlSource) {
                    Class extends StaticSqlSource> rawSqlSource = ((StaticSqlSource) staticSqlSource).getClass();
                    Field sqlInStatic = rawSqlSource.getDeclaredField("sql");
                    sqlInStatic.setAccessible(true);
                    String sqlStr = (String) sqlInStatic.get(staticSqlSource);
                    sqlInStatic.set(staticSqlSource, "/*FORCE_MASTER*/ " + sqlStr);
                }
            }
        }
        return invocation.proceed()
        }
}