關于spring集成mybatis如何實現mysql數據庫讀寫分離的實例分析

本文通過實例代碼給大家介紹了spring集成mybatis實現mysql數據庫讀寫分離,需要的朋友可以參考下

前言

?????? 在網站的用戶達到一定規模后,數據庫因為負載壓力過高而成為網站的瓶頸。幸運的是目前大部分的主流數據庫都提供主從熱備功能,通過配置兩臺數據庫主從關系,可以將一臺數據庫的數據更新同步到另一臺服務器上。網站利用數據庫的這一功能,實現數據庫讀寫分離,從而改善數據庫負載壓力。如下圖所示:

關于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&gt;[]?classz?=?target.getClass().getInterfaces();????????????//?獲取目標類的接口,?所以@DataSource需要寫在接口上  ????Class&gt;[]?parameterTypes?=?((MethodSignature)?point.getSignature())  ????????.getMethod().getParameterTypes();  ????try  ????{  ??????Method?m?=?classz[0].getMethod(method,?parameterTypes);  ??????if?(m?!=?null?&amp;&amp;?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關鍵字控制好織入順序;

總結

? 版權聲明
THE END
喜歡就支持一下吧
點贊10 分享