Spring動態配置多資料來源--多mysql從庫
阿新 • • 發佈:2019-02-11
一直做了網際網路的小專案,感覺小公司的效能瓶頸主要在資料庫端。大公司沒去過,不清楚~
一般用mysql資料庫做主從,讀寫分離,減少主庫的壓力。假設1主4從。4個從庫每次的訪問是隨機,壓力平攤。
先把搜來的貼出來。先記錄下,再去code實驗~
採用spring的AbstractRoutingDataSource就可以簡單的解決這個問題。下面是用ibatis的。單獨的spring mvc 實現也是用AbstractRoutingDataSource類
AbstractRoutingDataSource實現了javax.sql.DataSource介面,因此可以理解為一個虛擬的動態DataSource,在需要的時候根據上下文Context動態決定使用哪個資料來源。
下面這個是ibatis 的 沒實驗只是簡單看看,不過每次 service都要
- DbContextHolder.setDbType("2");
/******************************一 、複製來的例子 開始**********************************************/
- <beansxmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:jee="http://www.springframework.org/schema/jee"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
- http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">
- <!-- ========================= GENERAL DEFINITIONS ========================= -->
- <jee:jndi-lookupid="ds0"jndi-name="jdbc/ds0"/>
- <jee:jndi-lookupid="ds1"jndi-name="jdbc/ds1"/>
- <jee:jndi-lookupid="ds2"jndi-name="jdbc/ds2"/>
- <beanid="dataSource"class="com.xxx.xxx.util.DynamicDataSource">
- <propertyname="targetDataSources">
- <mapkey-type="java.lang.String">
- <entrykey="0"value-ref="ds0"/>
- <entrykey="1"value-ref="ds1"/>
- <entrykey="2"value-ref="ds2"/>
- </map>
- </property>
- <propertyname="defaultTargetDataSource"ref="1"/>
- </bean>
- <!-- SqlMap setup for iBATIS Database Layer -->
- <beanid="sqlMapClient"class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
- <propertyname="dataSource"ref="dataSource"/>
- <propertyname="configLocation"value="classpath:com/xxx/xxx/dao/sqlmap/sql-map-config.xml"/>
- </bean>
- <beanid="testDAO"class="com.xxx.xxx.dao.impl.TestDAO">
- <propertyname="sqlMapClient"ref="sqlMapClient"/>
- </bean>
- <beanid="testService"class="com.xxx.xxx.service.impl.TestService">
- <propertyname="testDAO"ref="testDAO"/>
- </bean>
- </beans>
其核心是DynamicDataSource,程式碼如下
Java程式碼- package com.xxx.xxx.util;
- import org.apache.log4j.Logger;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- publicclass DynamicDataSource extends AbstractRoutingDataSource {
- static Logger log = Logger.getLogger("DynamicDataSource");
- @Override
- protected Object determineCurrentLookupKey() {
- // TODO Auto-generated method stub
- return DbContextHolder.getDbType();
- }
- }
上下文DbContextHolder為一執行緒安全的ThreadLocal,如下
Java程式碼- package com.xxx.xxx.util;
- publicclass DbContextHolder {
- privatestaticfinal ThreadLocal contextHolder = new ThreadLocal();
- publicstaticvoid setDbType(String dbType) {
- contextHolder.set(dbType);
- }
- publicstatic String getDbType() {
- return (String) contextHolder.get();
- }
- publicstaticvoid clearDbType() {
- contextHolder.remove();
- }
- }
sql-map-config.xml如下
Xml程式碼- <?xmlversion="1.0"encoding="UTF-8"standalone="no"?>
- <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
- "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
- <sqlMapConfig>
- <sqlMapresource="com/xxx/xxx/dao/sqlmap/Object.xml"/>
- </sqlMapConfig>
這樣在呼叫service之前只需要設定一下上下文即可呼叫相應的資料來源,如下:
Java程式碼- DbContextHolder.setDbType("2");
- //execute services
- //.........
dao如下
Java程式碼- package com.xxx.xxx.dao.impl;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import org.apache.log4j.Logger;
- import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;
- import com.xxx.xxx.vo.TestObj;
- publicclass TestDAO extends SqlMapClientDaoSupport implements ITestDAO {
- static Logger log = Logger.getLogger(TestDAO.class);
- public TestObj getTestObj(String objID) throws Exception {
- return (TestObj) getSqlMapClientTemplate().queryForObject("getTestObj", objID);
- }
- }
/******************************一 、複製來的例子 結束**********************************************/
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- 資料庫配置檔案載入 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:/jdbc.properties</value>
</list>
</property>
</bean>
<!-- 資料來源parent-->
<bean id="parentDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close" abstract="true">
<property name="driverClass">
<value>${jdbc.driverClassName}</value>
</property>
<property name="jdbcUrl">
<value>${jdbc.url}</value>
</property>
<property name="user">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
<property name="maxPoolSize">
<value>${jdbc.maxPoolSize}</value>
</property>
<property name="minPoolSize">
<value>${jdbc.minPoolSize}</value>
</property>
<property name="initialPoolSize">
<value>${jdbc.initialPoolSize}</value>
</property>
<property name="idleConnectionTestPeriod">
<value>${jdbc.idleConnectionTestPeriod}
</value>
</property>
<property name="maxIdleTime">
<value>${jdbc.maxIdleTime}</value>
</property>
</bean>
<!-- 主庫資料來源-->
<bean id="writedb" parent="parentDataSource"></bean>
<!-- 從庫資料來源-->
<bean id="read02" destroy-method="close" parent="parentDataSource">
<property name="jdbcUrl">
<value>${jdbc.read.db02.url}</value>
</property>
<property name="user">
<value>${jdbc.read.db02.username}</value>
</property>
<property name="password">
<value>${jdbc.read.db02.password}</value>
</property>
</bean>
<!-- 從庫資料來源-->
<bean id="read03" destroy-method="close" parent="parentDataSource">
<property name="jdbcUrl">
<value>${jdbc.read.db03.url}</value>
</property>
<property name="user">
<value>${jdbc.read.db03.username}</value>
</property>
<property name="password">
<value>${jdbc.read.db03.password}</value>
</property>
</bean>
<!-- 動態資料來源 -->
<bean id="dataSource" class="com.share.common.database.DataSourceRouter">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="writedb" value-ref="writedb" />
<entry key="read02" value-ref="read02" />
<entry key="read03" value-ref="read03" />
</map>
</property>
<property name="defaultTargetDataSource" ref="writedb" />
<property name="dataSourceKey">
<ref local="dataSourceKey" />
</property>
</bean>
<!-- 讀寫管理 -->
<bean id="dataSourceKey" class="com.share.common.database.DataSourceKeyImpl">
<property name="readDateSourceMap">
<map key-type="java.lang.String">
<entry key="read02" value="read02" />
<entry key="read03" value="read03" />
</map>
</property>
<property name="writedbKey">
<value>writedb</value>
</property>
</bean>
<!-- 事務配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事務管理攔截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributes">
<props>
<prop key="query*">PROPAGATION_SUPPORTS,-Exception</prop>
<prop key="select*">PROPAGATION_SUPPORTS,-Exception</prop>
<prop key="find*">PROPAGATION_SUPPORTS,-Exception</prop>
<prop key="get*">PROPAGATION_SUPPORTS,-Exception</prop>
<prop key="save*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="edit*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="*">PROPAGATION_SUPPORTS,-Exception</prop>
</props>
</property>
</bean>
<!-- 動態資料來源攔截器 -->
<bean id="dataSourceInterceptor" class="com.share.common.database.DataSourceInterceptor">
<property name="attributes">
<props>
<prop key="query*">readdb</prop>
<prop key="select*">readdb</prop>
<prop key="find*">readdb</prop>
<prop key="get*">readdb</prop>
<prop key="save*">writedb</prop>
<prop key="update*">writedb</prop>
<prop key="delete*">writedb</prop>
<prop key="add*">writedb</prop>
<prop key="edit*">writedb</prop>
<prop key="*">readdb</prop>
</props>
</property>
<property name="dataSourceKey">
<ref bean="dataSourceKey" />
</property>
</bean>
<!-- 根據service名稱攔截 -->
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>dataSourceInterceptor</value>
</list>
</property>
</bean>
<!-- ibatis sqlMapClient-->
<bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation"
value="classpath:/config/ibatis/SqlMapConfig.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- dao 基類 -->
<bean id="simpleDao" class="com.share.common.dao.IbatisSimpleDaoImpl">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>
jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
jdbc.username=root
jdbc.password=root
jdbc.maxPoolSize=100
jdbc.minPoolSize=10
jdbc.initialPoolSize=10
jdbc.idleConnectionTestPeriod=900
jdbc.maxIdleTime=1800
jdbc.read.db02.url=jdbc:mysql://127.0.0.1:3306/test02?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
jdbc.read.db02.username=root
jdbc.read.db02.password=root
jdbc.read.db03.url=jdbc:mysql://127.0.0.1:3306/test03?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
jdbc.read.db03.username=root
jdbc.read.db03.password=root
最近忙沒時間都貼出來,有時間再上傳下全部的。
主要原理:
1.寫自己的Rout類繼承AbstractRoutingDataSource類
@Override determineCurrentLookupKey()方法
此方法返回dataSource的key.我們只要控制這個key.(例子中有3個writedb、 readdb02、readdb03 ),就是控制了每次使用的那個資料來源。
2.攔截器
寫自己的攔截器實現MethodInterceptor。在此攔截器中根據請求的方法名字設定key.(例子中有3個writedb、 readdb02、readdb03 )
然後在自己的Rout類中返回就可以了。
最終執行測試的結果:
迴圈10次查詢。10次讀的庫是隨機的,我測的時候配置了10個從庫.
/********************************************二、動態分配資料來源 spring2.5 +ibatis*************************************************/