原理解密→SpringAOP實現動態數據源(讀寫分離),實質上原理是什麼?

原理解密

咱們逐個講解其中涉及的點,而後串起來理解讀寫分離的底層原理spring

Spring AOP

AOP:Aspect Oriented Programsql

關於 Spring AOP,相信你們耳熟能詳,它是對 OOP 的一種補充,OOP 是縱向的,AOP 則是橫向的數據庫

bc6c40bde4d344338d7d186216f11968


如上圖所示,OOP 屬於一種縱向拓展,AOP 則是一種橫向拓展。AOP 依託於 OOP,將公共功能代碼抽象出來做爲一個切面,減小重複代碼量,下降耦合app

AOP 的底層實現是動態代理,具體的表現形式粗略以下ide

39101fd77bf84e84b46a401c2899c9c1


對 Spring AOP 有個大體瞭解了,咱們就能夠接着往下看了性能

Spring 數據源

不管是 Spring JDBC,仍是 Hibernate,亦或是 MyBatis,其實都是對 JDBC 的封裝;對於JDBC,咱們不要太熟,大致流程以下ui

70225eaa87724a18b2ac6ee115cac309


然而,在實際應用中,咱們每每不會直接使用 JDBC,而是使用 ORM,ORM 會封裝上述的流程,也就說咱們再也不須要關注了;MyBatis 使用步驟大體以下url

abe4bd1c1eb34a3fb72be8d64976fef9


咱們以 SpringBoot + pagehelper + Druid(ssm) 爲例,來看看具體是怎麼獲取 Connection 對象的spa

bbd73c70d407476296d8db3a06a06ff9


能夠看到,若是事務管理器中存在 Connection 對象,則直接返回,不然從數據源中獲取返回(同時也賦值給了事務管理器);當取到 Connection 對象後,後續的流程你們就很是清楚了線程

然而咱們不須要關注 Connection 對象,只須要關注數據源,爲何呢 ? 由於咱們的配置文件中配置的是數據源而不是 Connection,是否是頗有道理 ?

ThreadLocal

若是咱們須要在各層之間進行參數的傳遞,實現方式有哪些 ?

最多見的方式可能就是方法參數,但還有一種容易忽略的方式:ThreadLocal,能夠在當前線程內傳遞參數,減小方法的參數個數

關於 ThreadLocal,有興趣的能夠查看:結合ThreadLocal來看spring事務源碼,感覺下清泉般的洗滌!

當咱們熟悉上面的三點後,後面的就好理解了,接着往下看

動態數據源

一個數據源只能對應一個數據庫,若是咱們有多個數據庫(一主多從),那麼就須要配置多個數據源,相似以下

 <!-- master數據源 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!-- 基本屬性 url、user、password -->  
        <property name="driverClassName" value="${jdbc.driverClassName}" />  
        <property name="url" value="${jdbc.url}" />  
        <property name="username" value="${jdbc.username}" />  
        <property name="password" value="${jdbc.password}" />  
        <property name="initialSize" value="${jdbc.initialSize}" />  
        <property name="minIdle" value="${jdbc.minIdle}" />   
        <property name="maxActive" value="${jdbc.maxActive}" />  
        <property name="maxWait" value="${jdbc.maxWait}" />        <!-- 超過期間限制是否回收 -->
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}" />        <!-- 超過期間限制多長; -->
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />        <!-- 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />        <!-- 配置一個鏈接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />        <!-- 用來檢測鏈接是否有效的sql,要求是一個查詢語句-->
        <property name="validationQuery" value="${jdbc.validationQuery}" />        <!-- 申請鏈接的時候檢測 -->
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />        <!-- 申請鏈接時執行validationQuery檢測鏈接是否有效,配置爲true會下降性能 -->
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />        <!-- 歸還鏈接時執行validationQuery檢測鏈接是否有效,配置爲true會下降性能  -->
        <property name="testOnReturn" value="${jdbc.testOnReturn}" />    </bean>

    <!-- slave數據源 -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${slave.jdbc.driverClassName}" />  
        <property name="url" value="${slave.jdbc.url}" />  
        <property name="username" value="${slave.jdbc.username}" />  
        <property name="password" value="${slave.jdbc.password}" />  
        <property name="initialSize" value="${slave.jdbc.initialSize}" />  
        <property name="minIdle" value="${slave.jdbc.minIdle}" />   
        <property name="maxActive" value="${slave.jdbc.maxActive}" />  
        <property name="maxWait" value="${slave.jdbc.maxWait}" />        <property name="removeAbandoned" value="${slave.jdbc.removeAbandoned}" />        <property name="removeAbandonedTimeout" value="${slave.jdbc.removeAbandonedTimeout}" />        <property name="timeBetweenEvictionRunsMillis" value="${slave.jdbc.timeBetweenEvictionRunsMillis}" />        <property name="minEvictableIdleTimeMillis" value="${slave.jdbc.minEvictableIdleTimeMillis}" />        <property name="validationQuery" value="${slave.jdbc.validationQuery}" />        <property name="testWhileIdle" value="${slave.jdbc.testWhileIdle}" />        <property name="testOnBorrow" value="${slave.jdbc.testOnBorrow}" />        <property name="testOnReturn" value="${slave.jdbc.testOnReturn}" />    </bean>

但是事務管理器中只有一個數據源的引用

aa081d5256634512bbb4437e07f4a235


那怎麼對應咱們配置文件中的多個數據源呢 ?其實,咱們能夠自定義一個類 DynamicDataSource 來實現 DataSource,DynamicDataSource 中存儲咱們配置的多數據源,而後將 DynamicDataSource 的實例配置給事務管理器;當從事務管理器獲取 Connection 對象的時候,會從 DynamicDataSource 實例獲取,而後再由 DynamicDataSource 根據 routeKey 路由到某個具體的數據源,從中獲取 Connection;大致流程以下

81e823c4de024c3bbe84b2b81a6b7cd3


Spring 也考慮到了這一點,提供了一個抽象類:AbstractRoutingDataSource,DynamicDataSource 繼承它能夠爲咱們省很是多的代碼

56b07256a3114715a7dbcce24ec63c28


配置文件中增長以下配置

1a672ae808f34bca9c12cd632a8674ed


可是問題又來了,這個 routeKey 怎麼處理,也就說 DynamicDataSource 怎麼知道用哪一個數據源 ? AbstractRoutingDataSource 提供了一個方法: determineCurrentLookupKey 咱們只須要實現它,DynamicDataSource 就知道是使用哪一個 lookupKey (routeKey 在 Spring 中的命名)了;determineCurrentLookupKey 具體該如何實現了,咱們能夠結合 ThreadLocal 來實現;整個流程大體以下

    

fe8b5fa84e6345d8b1c82efd8f5421b6


一旦咱們在切面中指定了 lookupKey,那麼後續就會使用 lookupKey 對應的數據源來操做數據庫了

自此,相信你們已經明白了動態數據源的底層原理

總結

Spring AOP → 將咱們指定的 lookupKey 放入 ThreadLocal

ThreadLocal → 線程內共享 lookupKey

DynamicDataSource → 對多數據源進行封裝,根據 ThreadLocal 中的 lookupKey 動態選擇具體的數據源

若是咱們對其中的某個環節不懂,能夠試着刪掉它,而後看這個流程可否正常串起來,這樣就能明白各個環節的做用了

懸念

Spring AOP 實現多數據源,是否與 Spring 事務衝突 ,若衝突了該如何解決 ?

推薦閱讀:

https://www.bilibili.com/video/BV1HQ4y1M7oe/

https://www.bilibili.com/video/BV1BE411A78Z?p=3

https://www.bilibili.com/video/BV1GE411N7sc/

相關文章
相關標籤/搜索