2018-06-07 Mybatis 遇到MYSQL讀寫分離獲取主鍵的問題

問題

條件:MYSQL數(shù)據(jù)庫使用讀寫分離代理晾虑,例如使用阿里云的RDS+主從分離代理
對(duì)于一個(gè)簡(jiǎn)單的Insert偏形,獲取不到主鍵ID,getId()返回0阻星。

下面是常見的寫法:

 <insert id="insert" parameterType="com.github.slankka.domain.model.DalaoTest">
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
      SELECT LAST_INSERT_ID()
    </selectKey>
    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>

接下來是

  <insert id="insert" keyColumn="id"
 keyProperty="id" parameterType="com.github.slankka.domain.model.DalaoTest" useGeneratedKeys="true">
    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>

區(qū)別

這兩者都是獲取主鍵的寫法。
第一種
當(dāng)使用SelectKey時(shí)已添,Mybatis會(huì)使用SelectKeyGenerator妥箕,INSERT之后滥酥,多發(fā)送一次查詢語句,獲得主鍵值畦幢,在上述讀寫分離被代理的情況下坎吻,會(huì)得不到正確的主鍵。

第二種
當(dāng)MapperXML使用 useGeneratedKeys=true 不寫SelectKey節(jié)點(diǎn)呛讲,且當(dāng)Mybatis的配置中開啟useGeneratedKeys時(shí)禾怠,Mybatis會(huì)使用Jdbc3KeyGenerator, (可以參考下面parseStatementNode的注釋)
但需要注意的是,當(dāng)主鍵不是id的時(shí)候贝搁,需要定義

keyColumn="anotherId" keyProperty="anotherId" 

使用該KeyGenerator的好處就是直接在一次INSERT 語句內(nèi),通過resultSet獲取得生成的主鍵值芽偏,并很好的支持設(shè)置了讀寫分離代理的數(shù)據(jù)庫雷逆,例如阿里云RDS + 讀寫分離代理,無需指定主庫污尉。

原理

//org.apache.ibatis.builder.xml.XMLStatementBuilder
public void parseStatementNode() {
//省略其他代碼
//這里處理所有的SelectKey的節(jié)點(diǎn)膀哲,并存儲(chǔ)KeyGenerator
      // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

//這里如果找到了KeyGenerator 則,忽略配置文件的useGeneratedKeys被碗。
//如果XML不寫selectKey某宪,原來他是自動(dòng)根據(jù)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);

}

最后兴喂,在執(zhí)行INSERT之后,調(diào)用processAfter焚志。

public interface KeyGenerator {

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

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

}

具體Jdbc3KeyGenerator和selectKeyGenerator 的區(qū)別衣迷,可以查看源碼。

解決方案

根據(jù)上文所述酱酬,讓Mybatis 為INSERT或者UPDATE 使用Jdbc3KeyGenerator的方法就是壶谒,不寫SelectKey,并且開啟useGeneratedKeys=true膳沽。

<setting name="useGeneratedKeys" value="true"/>

另外一種臨時(shí)方案:
就是用攔截器讓SelectKey的語句汗菜,強(qiáng)制訪問主庫。

@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);

        //阿里云讀寫分離強(qiáng)制指定主庫方案: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()
        }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挑社,一起剝皮案震驚了整個(gè)濱河市陨界,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滔灶,老刑警劉巖普碎,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異录平,居然都是意外死亡麻车,警方通過查閱死者的電腦和手機(jī)缀皱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动猬,“玉大人啤斗,你說我怎么就攤上這事×蘖” “怎么了钮莲?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)彼水。 經(jīng)常有香客問我崔拥,道長(zhǎng),這世上最難降的妖魔是什么凤覆? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任链瓦,我火速辦了婚禮,結(jié)果婚禮上盯桦,老公的妹妹穿的比我還像新娘慈俯。我一直安慰自己,他們只是感情好拥峦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布贴膘。 她就那樣靜靜地躺著,像睡著了一般略号。 火紅的嫁衣襯著肌膚如雪刑峡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天璃哟,我揣著相機(jī)與錄音氛琢,去河邊找鬼。 笑死随闪,一個(gè)胖子當(dāng)著我的面吹牛阳似,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铐伴,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼撮奏,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了当宴?” 一聲冷哼從身側(cè)響起畜吊,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎户矢,沒想到半個(gè)月后玲献,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年捌年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓢娜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡礼预,死狀恐怖眠砾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情托酸,我是刑警寧澤褒颈,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站励堡,受9級(jí)特大地震影響谷丸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜应结,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一淤井、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摊趾,春花似錦、人聲如沸游两。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贱案。三九已至肛炮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宝踪,已是汗流浹背侨糟。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘩燥,地道東北人秕重。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像厉膀,于是被迫代替她去往敵國和親溶耘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 服鹅? MyBatis 是支持定制化 SQL凳兵、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,425評(píng)論 0 4
  • 病因:恐懼 過年回來北京已經(jīng)有三四天的時(shí)間,感覺在個(gè)人成長(zhǎng)和職業(yè)方面沒有往前進(jìn)展那么一點(diǎn)點(diǎn)企软。晚上睡不著覺庐扫,早上起不...
    忘憂草鳳閱讀 263評(píng)論 0 0
  • 從早上起床至現(xiàn)在,耳邊的鞭炮聲不絕于耳,好似只有一家不放形庭,這年真不算過铅辞。早覺被攪醒,心情一時(shí)郁悶中碘勉。 我們中國人啊...
    一片鴻葉閱讀 186評(píng)論 0 0
  • 好久沒有遇到這么虐心的影片了验靡。 至于為什么要看這部由俞飛鴻主演的《愛有來生》倍宾,主要還是因?yàn)楦]文濤。 文濤老師才華橫...
    石頭嘛嘛閱讀 336評(píng)論 0 0