咱們逐個講解其中涉及的點,而後串起來理解讀寫分離的底層原理spring
AOP:Aspect Oriented Programsql
關於 Spring AOP,相信你們耳熟能詳,它是對 OOP 的一種補充,OOP 是縱向的,AOP 則是橫向的數據庫
如上圖所示,OOP 屬於一種縱向拓展,AOP 則是一種橫向拓展。AOP 依託於 OOP,將公共功能代碼抽象出來做爲一個切面,減小重複代碼量,下降耦合app
AOP 的底層實現是動態代理,具體的表現形式粗略以下ide
對 Spring AOP 有個大體瞭解了,咱們就能夠接着往下看了性能
不管是 Spring JDBC,仍是 Hibernate,亦或是 MyBatis,其實都是對 JDBC 的封裝;對於JDBC,咱們不要太熟,大致流程以下ui
然而,在實際應用中,咱們每每不會直接使用 JDBC,而是使用 ORM,ORM 會封裝上述的流程,也就說咱們再也不須要關注了;MyBatis 使用步驟大體以下url
咱們以 SpringBoot + pagehelper + Druid(ssm) 爲例,來看看具體是怎麼獲取 Connection 對象的spa
能夠看到,若是事務管理器中存在 Connection 對象,則直接返回,不然從數據源中獲取返回(同時也賦值給了事務管理器);當取到 Connection 對象後,後續的流程你們就很是清楚了線程
然而咱們不須要關注 Connection 對象,只須要關注數據源,爲何呢 ? 由於咱們的配置文件中配置的是數據源而不是 Connection,是否是頗有道理 ?
若是咱們須要在各層之間進行參數的傳遞,實現方式有哪些 ?
最多見的方式可能就是方法參數,但還有一種容易忽略的方式: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>
但是事務管理器中只有一個數據源的引用
那怎麼對應咱們配置文件中的多個數據源呢 ?其實,咱們能夠自定義一個類 DynamicDataSource 來實現 DataSource,DynamicDataSource 中存儲咱們配置的多數據源,而後將 DynamicDataSource 的實例配置給事務管理器;當從事務管理器獲取 Connection 對象的時候,會從 DynamicDataSource 實例獲取,而後再由 DynamicDataSource 根據 routeKey 路由到某個具體的數據源,從中獲取 Connection;大致流程以下
Spring 也考慮到了這一點,提供了一個抽象類:AbstractRoutingDataSource,DynamicDataSource 繼承它能夠爲咱們省很是多的代碼
配置文件中增長以下配置
可是問題又來了,這個 routeKey 怎麼處理,也就說 DynamicDataSource 怎麼知道用哪一個數據源 ? AbstractRoutingDataSource 提供了一個方法: determineCurrentLookupKey 咱們只須要實現它,DynamicDataSource 就知道是使用哪一個 lookupKey (routeKey 在 Spring 中的命名)了;determineCurrentLookupKey 具體該如何實現了,咱們能夠結合 ThreadLocal 來實現;整個流程大體以下
一旦咱們在切面中指定了 lookupKey,那麼後續就會使用 lookupKey 對應的數據源來操做數據庫了
自此,相信你們已經明白了動態數據源的底層原理
Spring AOP → 將咱們指定的 lookupKey 放入 ThreadLocal
ThreadLocal → 線程內共享 lookupKey
DynamicDataSource → 對多數據源進行封裝,根據 ThreadLocal 中的 lookupKey 動態選擇具體的數據源
若是咱們對其中的某個環節不懂,能夠試着刪掉它,而後看這個流程可否正常串起來,這樣就能明白各個環節的做用了
Spring AOP 實現多數據源,是否與 Spring 事務衝突 ,若衝突了該如何解決 ?
推薦閱讀:
https://www.bilibili.com/video/BV1HQ4y1M7oe/