本文通過實例代碼給大家介紹了spring集成mybatis實現mysql數據庫讀寫分離,需要的朋友可以參考下
前言
?????? 在網站的用戶達到一定規模后,數據庫因為負載壓力過高而成為網站的瓶頸。幸運的是目前大部分的主流數據庫都提供主從熱備功能,通過配置兩臺數據庫主從關系,可以將一臺數據庫的數據更新同步到另一臺服務器上。網站利用數據庫的這一功能,實現數據庫讀寫分離,從而改善數據庫負載壓力。如下圖所示:
應用服務器在寫數據的時候,訪問主數據庫,主數據庫通過主從復制機制將數據更新同步到從數據庫,這樣當應用服務器讀數據的時候,就可以通過從數據庫獲得數據。為了便于應用程序訪問讀寫分離后的數據庫,通常在應用服務器使用專門的數據庫訪問模塊,使數據庫讀寫分離對應用透明。
?????? 而本博客就是來實現“專門的數據庫訪問模塊”,使數據庫讀寫分離對應用透明。另外,mysql數據庫的主從復制可以參考我的mysql5.7.18的安裝與主從復制。注意,數據庫實現了主從復制,才能做數據庫的讀寫分離,所以,沒有實現數據庫主從復制的記得先去實現數據庫的主從復制
配置讀寫數據源(主從數據庫)
?????? mysqldb.properties
#主數據庫數據源 jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://192.168.0.4:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false jdbc.username=root jdbc.password=123456 jdbc.initialSize=1 jdbc.minIdle=1 jdbc.maxActive=20 jdbc.maxWait=60000 jdbc.removeAbandoned=true jdbc.removeAbandonedTimeout=180 jdbc.timeBetweenEvictionRunsMillis=60000 jdbc.minEvictableIdleTimeMillis=300000 jdbc.validationQuery=SELECT?1 jdbc.testWhileIdle=true jdbc.testOnBorrow=false jdbc.testOnReturn=false #從數據庫數據源 slave.jdbc.driverClassName=com.mysql.jdbc.Driver slave.jdbc.url=jdbc:mysql://192.168.0.221:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false slave.jdbc.username=root slave.jdbc.password=123456 slave.jdbc.initialSize=1 slave.jdbc.minIdle=1 slave.jdbc.maxActive=20 slave.jdbc.maxWait=60000 slave.jdbc.removeAbandoned=true slave.jdbc.removeAbandonedTimeout=180 slave.jdbc.timeBetweenEvictionRunsMillis=60000 slave.jdbc.minEvictableIdleTimeMillis=300000 slave.jdbc.validationQuery=SELECT?1 slave.jdbc.testWhileIdle=true slave.jdbc.testOnBorrow=false slave.jdbc.testOnReturn=false
主、從數據庫的地址記得改成自己的,賬號和密碼也需要改成自己的;其他配置項,大家可以酌情自行設置
?????? mybatis-spring.xml
<?xml version="1.0" encoding="UTF-8"?><beans> ??<!-- master數據源 --> ??<bean> ????<!-- 基本屬性 url、user、password -->? ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>?? ????<property></property>? ????<property></property> ????<!-- 超過時間限制是否回收 --> ????<property></property> ????<!-- 超過時間限制多長; --> ????<property></property> ????<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 --> ????<property></property> ????<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 --> ????<property></property> ????<!-- 用來檢測連接是否有效的sql,要求是一個查詢語句--> ????<property></property> ????<!-- 申請連接的時候檢測 --> ????<property></property> ????<!-- 申請連接時執行validationQuery檢測連接是否有效,配置為true會降低性能 --> ????<property></property> ????<!-- 歸還連接時執行validationQuery檢測連接是否有效,配置為true會降低性能 --> ????<property></property> ??</bean> ??<!-- slave數據源 --> ??<bean> ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>? ????<property></property>?? ????<property></property>? ????<property></property> ????<property></property> ????<property></property> ????<property></property> ????<property></property> ????<property></property> ????<property></property> ????<property></property> ????<property></property> ??</bean> ??<!-- 動態數據源,根據service接口上的注解來決定取哪個數據源 --> ??<bean>? ????<property>??? ?????<map>??? ???????<!-- write or slave -->?? ???????<entry></entry>??? ???????<!-- read or master -->? ???????<entry></entry>??? ?????</map>???????? ????</property>?? ????<property></property>??? ??</bean> ??<!-- Mybatis文件 --> ??<bean> ????<property></property>? ????<property></property> ????<!-- 映射文件路徑 --> ????<property></property> ??</bean> ??<bean> ????<property></property> ????<property></property> ??</bean> ??<!-- 事務管理器 --> ??<bean> ????<property></property> ??</bean> ??<!-- 聲明式開啟 --> ??<annotation-driven></annotation-driven> ??<!-- 為業務邏輯層的方法解析@DataSource注解 為當前線程的HandleDataSource注入數據源 -->?? ??<bean></bean>?? ??<config>?? ????<aspect>?? ??????<pointcut></pointcut>?? ??????<before></before>???????? ????</aspect>?? ??</config></beans>
AOP實現數據源的動態切換
DataSource.java
package?com.yzb.util; import?java.lang.annotation.ElementType; import?java.lang.annotation.Retention; import?java.lang.annotation.RetentionPolicy; import?java.lang.annotation.Target; /**? ?*?RUNTIME? ?*?編譯器將把注釋記錄在類文件中,在運行時?VM?將保留注釋,因此可以反射性地讀取。? ?*? ?*/? @Retention(RetentionPolicy.RUNTIME)? @Target(ElementType.METHOD)? public?@interface?DataSource { ??String?value(); }
DataSourceAspect.java
package?com.yzb.util; import?java.lang.reflect.Method; import?org.aspectj.lang.JoinPoint; import?org.aspectj.lang.reflect.MethodSignature; public?class?DataSourceAspect { ??/** ???*?在dao層方法獲取datasource對象之前,在切面中指定當前線程數據源 ???*/ ??public?void?before(JoinPoint?point) ??{ ????Object?target?=?point.getTarget(); ????String?method?=?point.getSignature().getName(); ????Class>[]?classz?=?target.getClass().getInterfaces();????????????//?獲取目標類的接口,?所以@DataSource需要寫在接口上 ????Class>[]?parameterTypes?=?((MethodSignature)?point.getSignature()) ????????.getMethod().getParameterTypes(); ????try ????{ ??????Method?m?=?classz[0].getMethod(method,?parameterTypes); ??????if?(m?!=?null?&&?m.isAnnotationPresent(DataSource.class)) ??????{ ????????DataSource?data?=?m.getAnnotation(DataSource.class); ????????System.out.println("用戶選擇數據庫庫類型:"?+?data.value()); ????????HandleDataSource.putDataSource(data.value());????????????//?數據源放到當前線程中 ??????} ????}?catch?(Exception?e) ????{ ??????e.printStackTrace(); ????} ??} ??}
DynamicDataSource.java
package?com.yzb.util; import?org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public?class?DynamicDataSource?extends?AbstractRoutingDataSource { ??/** ???*?獲取與數據源相關的key?此key是Map<string>?resolvedDataSources?中與數據源綁定的key值 ???*?在通過determineTargetDataSource獲取目標數據源時使用 ???*/ ??@Override ??protected?Object?determineCurrentLookupKey() ??{ ????return?HandleDataSource.getDataSource(); ??} }</string>
HandleDataSource.java
package?com.yzb.util; public?class?HandleDataSource { ??public?static?final?ThreadLocal<string>?holder?=?new?ThreadLocal<string>(); ??/** ???*?綁定當前線程數據源 ???*? ???*?@param?key ???*/ ??public?static?void?putDataSource(String?datasource) ??{ ????holder.set(datasource); ??} ??/** ???*?獲取當前線程的數據源 ???*? ???*?@return ???*/ ??public?static?String?getDataSource() ??{ ????return?holder.get(); ??} }</string></string>
service接口上應用@DataSource實現數據源的指定
package?com.yzb.service; import?java.util.List; import?com.yzb.model.Person; import?com.yzb.util.DataSource; public?interface?IPersonService?{ ??/** ???*?加載全部的person ???*?@return ???*/ ??List<person>?listAllPerson(); ??/** ???*?查詢某個人的信息 ???*?@param?personId ???*?@return ???*/ ??@DataSource("slave")??????//?指定使用從數據源 ??Person?getPerson(int?personId); ??boolean?updatePerson(Person?person); }</person>
注意點
測試的時候,怎么樣知道讀取的是從數據庫了? 我們可以修改從數據庫中查詢到的那條記錄的某個字段的值,以區分主、從數據庫;
?????? 事務需要注意,盡量保證在一個數據源上進行事務;
?????? 當某個service上有多個aop時,需要注意aop織入的順序問題,利用order關鍵字控制好織入順序;
總結