spring下的數(shù)據(jù)庫主從分離(下)

上一篇介紹了如何配置并使用動態(tài)數(shù)據(jù)源切換搞乏,這邊主要梳理下源碼原理和遇到的坑咬最。

1蕴轨、首先就是我們發(fā)現(xiàn)有事務(wù)的方法里面數(shù)據(jù)源切換是失敗的,并且都是用的主數(shù)據(jù)源贸营。

這里我們猜想是事務(wù)aop要比我們數(shù)據(jù)源aop先執(zhí)行了吨述,拿到默認(rèn)數(shù)據(jù)源,并不再改變钞脂。然后我把配置文件的默認(rèn)數(shù)據(jù)源設(shè)置為讀數(shù)據(jù)源揣云,發(fā)現(xiàn)代碼拿到的就是讀數(shù)據(jù)源,導(dǎo)致寫操作失敗冰啃,然后我開始debug源碼邓夕。

首先是aop執(zhí)行順序問題,這里可以根據(jù)order指定阎毅,但是不指定順序時焚刚,spring的聲明式事務(wù)的aop要比我們自定義的數(shù)據(jù)源aop先執(zhí)行。

我看了幾個人的文章扇调,并自己debug了下矿咕,梳理了一下事務(wù)aop的動作
(1)訪問到方法時,進(jìn)入代理類CglibAopProxy的靜態(tài)內(nèi)部類DynamicAdvisedInterceptor的intercept()方法狼钮,通過493行拿到一個這個方法的代理鏈碳柱,然后在499行執(zhí)行proceed()方法處理代理。

image.png

(2)進(jìn)入proceed()方法熬芜,我們看到這是個遞歸莲镣,這里循環(huán)的處理了代理鏈的每個代理,當(dāng)處理事務(wù)時涎拉,73行的invoke()方法進(jìn)入TransactionInterceptor的invoke()方法瑞侮。

image.png

(3)TransactionInterceptor的invoke()方法調(diào)用TransactionAspectSupport的invokeWithinTransaction方法


image.png

(4)invokeWithinTransaction方法里面150行調(diào)用createTransactionIfNecessary方法開始創(chuàng)建事務(wù),這里會根據(jù)事務(wù)屬性判斷是否需要開啟一個事務(wù)曼库,事務(wù)的傳遞性也是在這里判斷的区岗。

image.png

(5)createTransactionIfNecessary方法219行略板,調(diào)用getTransaction方法調(diào)用到AbstractPlatformTransactionManager類的getTransaction方法毁枯,然后進(jìn)入到doBegin方法。

image.png
image.png

(6)終于doBegin方法就來到了我們配置文件配置的DataSourceTransactionManager叮称,79行這里開始獲取connection了种玛,這個dataSource就是我們初始化設(shè)置進(jìn)去動態(tài)數(shù)據(jù)源藐鹤。然后就調(diào)用了AbstractRoutingDataSource的getConnection()方法了,注意這里txObject設(shè)置了一個ConnectionHolder赂韵,下面分析會用到娱节。

image.png

(7)看到這里就明白了,getConnection()調(diào)用determineTargetDataSource()方法祭示,這個方法里面調(diào)用了我們重寫過的determineCurrentLookupKey方法肄满,然而這時我們還沒走到數(shù)據(jù)源aop設(shè)置數(shù)據(jù)源,所以這里拿到的是null质涛,然后按照他這個邏輯就拿了默認(rèn)數(shù)據(jù)源稠歉,即主數(shù)據(jù)源。所以開啟事務(wù)的方法獲取到的是主數(shù)據(jù)源汇陆,在不指定order的前提下怒炸,一般也不會指定order。

image.png

然而有一個坑會導(dǎo)致這里獲取到讀數(shù)據(jù)源毡代,導(dǎo)致失敗阅羹,這個以后會分析。我繼續(xù)梳理下拿到寫數(shù)據(jù)源后教寂,為什么切換數(shù)據(jù)源會失敗捏鱼。


2、事務(wù)開啟后切換數(shù)據(jù)源為什么失敗

spring聲明式事務(wù)是面向連接connection的酪耕,如果connection變化穷躁,那么將導(dǎo)致事務(wù)無法進(jìn)行、提交因妇。所以事務(wù)一旦獲取connection就不會變化问潭,這也是為什么事務(wù)開啟后切換數(shù)據(jù)源失敗,因?yàn)橐呀?jīng)不再從我們的數(shù)據(jù)源aop里面拿connection了婚被。因?yàn)闀纳衔闹刑岬降腃onnectionHolder里面獲取connection狡忙。看下SpringManagedTransaction類址芯。

package org.mybatis.spring.transaction;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

public class SpringManagedTransaction implements Transaction {
  private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
  private final DataSource dataSource;
  private Connection connection;
  private boolean isConnectionTransactional;
  private boolean autoCommit;

  public SpringManagedTransaction(DataSource dataSource) {
    Assert.notNull(dataSource, "No DataSource specified");
    this.dataSource = dataSource;
  }

  public Connection getConnection() throws SQLException {
    if(this.connection == null) {
      this.openConnection();
    }

    return this.connection;
  }

  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    if(LOGGER.isDebugEnabled()) {
      LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional?" ":" not ") + "be managed by Spring");
    }

  }

  public void commit() throws SQLException {
    if(this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if(LOGGER.isDebugEnabled()) {
        LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
      }

      this.connection.commit();
    }

  }

  public void rollback() throws SQLException {
    if(this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
      if(LOGGER.isDebugEnabled()) {
        LOGGER.debug("Rolling back JDBC Connection [" + this.connection + "]");
      }

      this.connection.rollback();
    }

  }

  public void close() throws SQLException {
    DataSourceUtils.releaseConnection(this.connection, this.dataSource);
  }

  public Integer getTimeout() throws SQLException {
    ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
    return holder != null && holder.hasTimeout()?Integer.valueOf(holder.getTimeToLiveInSeconds()):null;
  }
}

執(zhí)行我們的業(yè)務(wù)代碼時灾茁,當(dāng)遇到數(shù)據(jù)庫dao執(zhí)行時,mybatis會走一些列流程谷炸,將我們配置好的configuration用來開啟一個openSession北专,然后調(diào)用SpringManagedTransaction的構(gòu)造方法,將datasource初始化進(jìn)去旬陡。然后各種用于執(zhí)行sql的BaseExecutor之類的調(diào)用SpringManagedTransaction類中的getConnection()方法拓颓,這個方法會首先判斷當(dāng)前類實(shí)例中的connection是否為空,不為空返回描孟,為空進(jìn)行獲取驶睦,這里如果釋放過close()方法砰左,或沒加載過會為空。獲取進(jìn)入openConnection()方法场航,通過DataSourceUtils的getConnection獲取缠导,這個方法比較重要,首先會判斷我們之前開啟事務(wù)設(shè)置的ConnectionHolder是否為空溉痢,不為空就返回ConnectionHolder的connection僻造,就是我們一開始處理
代理時候的xml默認(rèn)數(shù)據(jù)源,主數(shù)據(jù)源孩饼。如果沒開啟事務(wù)嫡意,這個ConnectionHolder是空的,就執(zhí)行Connection con = dataSource.getConnection();捣辆,從我們配置的AbstractRoutingDataSource里面拿數(shù)據(jù)源蔬螟,這就會走到我們通過數(shù)據(jù)源標(biāo)簽設(shè)置的數(shù)據(jù)源。

image.png

SpringManagedTransaction里面還有個close()方法汽畴,他是釋放connection的方法旧巾,發(fā)現(xiàn)沒有事務(wù)的時候,他會每次執(zhí)行一個dao會釋放一次忍些,如果在事務(wù)內(nèi)鲁猩,他不會釋放掉,直到事物提交才會執(zhí)行罢坝。這樣廓握,如果在事務(wù)內(nèi)有多條dao操作的時候,在第一個dao操作從ConnectionHolder拿到connection后不釋放嘁酿,后面的dao操作拿connection時進(jìn)入的getConnection()的判空就會返回非空隙券,直接返回connection,這就是事務(wù)保證唯一connection的方式闹司。


3娱仔、ThreadLocal使用時遇到tomcat線程池需要注意的坑。

上面說道游桩,事務(wù)aop先執(zhí)行會獲取到默認(rèn)數(shù)據(jù)源牲迫,因?yàn)槲覀兊臄?shù)據(jù)源標(biāo)簽還沒有加載,但是在調(diào)試過程中借卧,事務(wù)開啟時的determineCurrentLookupKey不為null盹憎,而是從庫。這是一次新的請求铐刘,理論上應(yīng)該是空陪每,因?yàn)檫€沒有任何時機(jī)將ThreadLocal設(shè)置值,然后注意到使用tomcat7時的bio時,只有我一個人請求時奶稠,會分配給我同一個線程,猜想是線程池復(fù)用線程捡遍,所以沒有將ThreadLocal里面的值銷毀锌订,導(dǎo)致這次請求拿到了上一個請求的在線程里面ThreadLocal的值,這樣還是比較危險的画株,所以在切面類里面將最初的before切入改成了around切入辆飘,在執(zhí)行后將ThreadLocal的值remove掉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谓传,一起剝皮案震驚了整個濱河市蜈项,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌续挟,老刑警劉巖紧卒,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诗祸,居然都是意外死亡跑芳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門直颅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來博个,“玉大人,你說我怎么就攤上這事功偿。” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我谐算,道長,這世上最難降的妖魔是什么往果? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮肮之,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己凯傲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布浴栽。 她就那樣靜靜地躺著,像睡著了一般嫁乘。 火紅的嫁衣襯著肌膚如雪睁冬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天脚线,我揣著相機(jī)與錄音丰涉,去河邊找鬼肛度。 笑死冠骄,一個胖子當(dāng)著我的面吹牛职烧,可吹牛的內(nèi)容都是我干的蝗敢。 我是一名探鬼主播寿谴,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼失受!你這毒婦竟也來了讶泰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤拂到,失蹤者是張志新(化名)和其女友劉穎峻厚,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谆焊,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惠桃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辜王。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡劈狐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呐馆,到底是詐尸還是另有隱情肥缔,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布汹来,位于F島的核電站续膳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏收班。R本人自食惡果不足惜坟岔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摔桦。 院中可真熱鬧社付,春花似錦、人聲如沸邻耕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兄世。三九已至啼辣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間御滩,已是汗流浹背鸥拧。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留艾恼,地道東北人住涉。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像钠绍,于是被迫代替她去往敵國和親舆声。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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