一.讀寫分離原理:
上一篇文章我們已經講解了mysql主從複製的原理並且實現了 只要往主服務器中插入數據 那麼從數據庫slaver將會跟着同步主服務器master的數據
那麼我們java 代碼來實現的話 只要動態切換數據庫 就達到了讀寫分離的目的。本文中是用spring + mybatis 來整合案例的 那麼我們如果能夠做到動態
的切換spring的數據源 從而就可以達到切換數據庫的目的
二.spring實現切換數據庫原理:
通過Spring的AOP思想來實現 根據你訪問的service方法名來判斷此方法執行查詢還是執行更改操作
一般查詢切換到從數據源 更改插入刪除操作在主數據庫 因爲主數據庫會同步到從數據庫
此時我們spring-mybatis.xml 文件中至少應該有兩個數據源 masterDataSource 和slaverDataSource 稍後貼出
masterDataSource數據源配置 和我們普通的配置沒什麼差別(數據庫信息從配置文件讀取)
<!-- 主庫 用來寫數據 -->
<bean id="dataSourceWriter" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 連接池最大數量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 連接池最大空閒 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 連接池最小空閒 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
<bean id="dataSourceWriter" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 連接池最大數量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 連接池最大空閒 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 連接池最小空閒 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
slaverDataSource 數據源
<!-- 從庫用來讀數據 -->
<bean id="dataSourceReader" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${slaver.driver}" />
<property name="url" value="${slaver.url}" />
<property name="username" value="${slaver.username}" />
<property name="password" value="${slaver.password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="${slaver.initialSize}"></property>
<!-- 連接池最大數量 -->
<property name="maxActive" value="${slaver.maxActive}"></property>
<!-- 連接池最大空閒 -->
<property name="maxIdle" value="${slaver.maxIdle}"></property>
<!-- 連接池最小空閒 -->
<property name="minIdle" value="${slaver.minIdle}"></property>
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="${slaver.maxWait}"></property>
</bean>
<bean id="dataSourceReader" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${slaver.driver}" />
<property name="url" value="${slaver.url}" />
<property name="username" value="${slaver.username}" />
<property name="password" value="${slaver.password}" />
<!-- 初始化連接大小 -->
<property name="initialSize" value="${slaver.initialSize}"></property>
<!-- 連接池最大數量 -->
<property name="maxActive" value="${slaver.maxActive}"></property>
<!-- 連接池最大空閒 -->
<property name="maxIdle" value="${slaver.maxIdle}"></property>
<!-- 連接池最小空閒 -->
<property name="minIdle" value="${slaver.minIdle}"></property>
<!-- 獲取連接最大等待時間 -->
<property name="maxWait" value="${slaver.maxWait}"></property>
</bean>
我們原來sqlSessionFactory的bean中的屬性dataSource 應該指向我們包含主數據源和從數據源的bean
所以我們要定義一個dynamicDataSource 來管理masterDatas 和slaverDataSource 代碼如下
<!-- 動態數據源 -->
<bean id="dynamicDataSource" class="com.cn.wx.db.DynamicDataSource">
<!-- 通過key-value關聯數據源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dataSourceWriter" key="dataSourceWriter"></entry>
<entry value-ref="dataSourceReader" key="dataSourceReader"></entry>
</map>
</property>
<!-- 默認的DataSource配置-->
<property name="defaultTargetDataSource" ref="dataSourceWriter" />
</bean>
<bean id="dynamicDataSource" class="com.cn.wx.db.DynamicDataSource">
<!-- 通過key-value關聯數據源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dataSourceWriter" key="dataSourceWriter"></entry>
<entry value-ref="dataSourceReader" key="dataSourceReader"></entry>
</map>
</property>
<!-- 默認的DataSource配置-->
<property name="defaultTargetDataSource" ref="dataSourceWriter" />
</bean>
此bean的class是我們自定義的class 需要集成 spring中的 AbstractRoutingDataSource重寫一下方法
@Override
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return DBContextHolder.getDbType();
}
protected Object determineCurrentLookupKey() {
// TODO Auto-generated method stub
return DBContextHolder.getDbType();
}
DBContextHolder 類是我們自己定義的 用來返回與當前線程綁定的數據源的名稱
核心代碼如下:
/**
* 線程threadlocal
*/
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
private static String DEFAUL_DB_TYPE_WRITER = "dataSourceWriter";
/* 獲取本線程的dbtype
* @return
*/
public static String getDbType() {
String db = contextHolder.get();
if (db == null) {
db = DEFAUL_DB_TYPE_WRITER;// 默認是讀寫庫
}
return db;
}
* 線程threadlocal
*/
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
private static String DEFAUL_DB_TYPE_WRITER = "dataSourceWriter";
/* 獲取本線程的dbtype
* @return
*/
public static String getDbType() {
String db = contextHolder.get();
if (db == null) {
db = DEFAUL_DB_TYPE_WRITER;// 默認是讀寫庫
}
return db;
}
3. spring AOP配置實現數據源切換:
我們訪問以 select* get* find* query* 開頭的Service方法都會切換到讀的數據源 直接上代碼
<!-- 數據源讀寫分離 aop -->
<bean id="dynamicDataSourceAOP" class="com.cn.wx.db.DynamicDataSourceAOP">
<property name="methods">
<map>
<entry key="select*" value="dataSourceReader" />
<entry key="get*" value="dataSourceReader" />
<entry key="find*" value="dataSourceReader" />
<entry key="page*" value="dataSourceReader" />
<entry key="query*" value="dataSourceReader" />
</map>
</property>
<property name="defaultDataSource" value="dataSourceWriter"/>
</bean>
<bean id="dynamicDataSourceAOP" class="com.cn.wx.db.DynamicDataSourceAOP">
<property name="methods">
<map>
<entry key="select*" value="dataSourceReader" />
<entry key="get*" value="dataSourceReader" />
<entry key="find*" value="dataSourceReader" />
<entry key="page*" value="dataSourceReader" />
<entry key="query*" value="dataSourceReader" />
</map>
</property>
<property name="defaultDataSource" value="dataSourceWriter"/>
</bean>
DynamicDataSourceAOP 是我們自己來根據service方法不同來切換數據源的核心邏輯 如下:
spring-mybatis.xml 中在原來的aop:config中加入AOP的切面如下:
至此其實我們的配置已經完畢 接下來就是測試讀寫分離是否成功。
4.測試讀寫分離配置是否成功:
接下我們來測試是否成功的實現了讀寫分離
我們插入往主數據庫中插入一條數據 這個時候去查看從數據庫 是否也有這條記錄 有的話說明成功!
繼續驗證讀 是否從從數據庫中讀取的 代碼如下:
,注意查詢方法是AOP中攔截到的 數據源纔會去切換到slaverDataSource
此時主從數據庫一樣怎麼驗證查詢的是從數據庫呢 很簡單 我們手動去修改從數據庫的某一個字段值 除了按條件查詢的值以外 都可以
此時我們主數據庫數據如下:
從數據庫數據如下:
修改從數據庫的age 爲28 此時主數據庫還是原來的25
如果查詢的結果是28那麼讀寫分離就已經實現了
現在執行查詢方法
sql語句打印的age是28 到此爲止 就已經實現了讀寫分離