2018-06-07 Mybatis MYSQL読み書き分離マスターキー取得の問題に遭遇
5460 ワード
に質問
条件:MYSQLデータベースは読み書き分離エージェントを使用し、例えばアリクラウドのRDS+プライマリスレーブ分離エージェントを使用して簡単なInsertに対してプライマリキーIDが取得されず、getId()は0を返す.
以下は一般的な書き方です.
次は
区別する
どちらもプライマリ・キーを取得する書き方です.1つ目は、SelectKeysを使用する場合、MybatisはSelectKeyGenerator,INSERTを使用した後、クエリー文を1回多く送信してプライマリ・キー値を取得し、上記読み書き分離がエージェントされている場合、正しいプライマリ・キーが得られないことです.
2つ目は、MapperXMLがuseGeneratedKeys=trueを使用してSelectKeyノードを書かず、Mybatisの構成でuseGeneratedKeysをオンにすると、MybatisはJdbc 3 KeyGeneratorを使用します(以下のparseStatementNodeの注釈を参照できます)ただし、プライマリ・キーがidでない場合は定義する必要があります
このKeyGeneratorを使用するメリットは、1回のINSERT文内でresultSetを介して生成されたプライマリ・キー値を直接取得し、プライマリ・ライブラリを指定することなく、読み書き分離エージェントを設定したデータベースをサポートすることです.
げんり
最後にINSERTを実行した後、processAfterを呼び出す.
具体的なJdbc 3 KeyGeneratorとselectKeyGeneratorの違いは、ソースコードを表示できます.
ソリューション
前述したように、MybatisがINSERTまたはUPDATEのためにJdbc 3 KeyGeneratorを使用する方法は、SelectKeyを書かずにuseGeneratedKeys=trueをオンにすることである.
もう1つの一時的なシナリオは、ブロックでSelectKeyの文を強制的にマスターライブラリにアクセスさせることです.
条件: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()
}
}