1. 程式人生 > >Spring動態配置多資料來源--多mysql從庫

Spring動態配置多資料來源--多mysql從庫


一直做了網際網路的小專案,感覺小公司的效能瓶頸主要在資料庫端。大公司沒去過,不清楚鄙視~

一般用mysql資料庫做主從,讀寫分離,減少主庫的壓力。假設1主4從。4個從庫每次的訪問是隨機,壓力平攤。

先把搜來的貼出來。先記錄下,再去code實驗~

採用spring的AbstractRoutingDataSource就可以簡單的解決這個問題。下面是用ibatis的。單獨的spring mvc 實現也是用AbstractRoutingDataSource類

AbstractRoutingDataSource實現了javax.sql.DataSource介面,因此可以理解為一個虛擬的動態DataSource,在需要的時候根據上下文Context動態決定使用哪個資料來源。

下面這個是ibatis 的 沒實驗只是簡單看看,不過每次 service都要

  1. DbContextHolder.setDbType("2");  
從庫多了哥們都設定暈了。稍後學習奉上根據事務管理直接動態隨機分配的~ (9.21更新 。在最下面的第2部分是根據公司的專案整理出來的。測試通過,可以動態分配)

/******************************一 、複製來的例子 開始**********************************************/

Xml程式碼 複製程式碼
  1. <beansxmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:aop="http://www.springframework.org/schema/aop"
  4. xmlns:tx="http://www.springframework.org/schema/tx"
  5. xmlns:jee="http://www.springframework.org/schema/jee"
  6. xsi:schemaLocation="   
  7.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd   
  8.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd   
  9.             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd   
  10.             http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">
  11. <!-- ========================= GENERAL DEFINITIONS ========================= -->
  12. <jee:jndi-lookupid="ds0"jndi-name="jdbc/ds0"/>
  13. <jee:jndi-lookupid="ds1"jndi-name="jdbc/ds1"/>
  14. <jee:jndi-lookupid="ds2"jndi-name="jdbc/ds2"/>
  15. <beanid="dataSource"class="com.xxx.xxx.util.DynamicDataSource">
  16. <propertyname="targetDataSources">
  17. <mapkey-type="java.lang.String">
  18. <entrykey="0"value-ref="ds0"/>
  19. <entrykey="1"value-ref="ds1"/>
  20. <entrykey="2"value-ref="ds2"/>
  21. </map>
  22. </property>
  23. <propertyname="defaultTargetDataSource"ref="1"/>
  24. </bean>
  25. <!-- SqlMap setup for iBATIS Database Layer -->
  26. <beanid="sqlMapClient"class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  27. <propertyname="dataSource"ref="dataSource"/>
  28. <propertyname="configLocation"value="classpath:com/xxx/xxx/dao/sqlmap/sql-map-config.xml"/>
  29. </bean>
  30. <beanid="testDAO"class="com.xxx.xxx.dao.impl.TestDAO">
  31. <propertyname="sqlMapClient"ref="sqlMapClient"/>
  32. </bean>
  33. <beanid="testService"class="com.xxx.xxx.service.impl.TestService">
  34. <propertyname="testDAO"ref="testDAO"/>
  35. </bean>
  36. </beans>

其核心是DynamicDataSource,程式碼如下

Java程式碼 複製程式碼
  1. package com.xxx.xxx.util;   
  2. import org.apache.log4j.Logger;   
  3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;   
  4. publicclass DynamicDataSource extends AbstractRoutingDataSource {   
  5. static Logger log = Logger.getLogger("DynamicDataSource");   
  6. @Override
  7. protected Object determineCurrentLookupKey() {   
  8. // TODO Auto-generated method stub
  9. return DbContextHolder.getDbType();   
  10.     }   
  11. }  

上下文DbContextHolder為一執行緒安全的ThreadLocal,如下

Java程式碼 複製程式碼
  1. package com.xxx.xxx.util;   
  2. publicclass DbContextHolder {   
  3. privatestaticfinal ThreadLocal contextHolder = new ThreadLocal();   
  4. publicstaticvoid setDbType(String dbType) {   
  5.         contextHolder.set(dbType);   
  6.     }   
  7. publicstatic String getDbType() {   
  8. return (String) contextHolder.get();   
  9.     }   
  10. publicstaticvoid clearDbType() {   
  11.         contextHolder.remove();   
  12.     }   
  13. }  

sql-map-config.xml如下

Xml程式碼 複製程式碼
  1. <?xmlversion="1.0"encoding="UTF-8"standalone="no"?>
  2. <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"   
  3.         "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
  4. <sqlMapConfig>
  5. <sqlMapresource="com/xxx/xxx/dao/sqlmap/Object.xml"/>
  6. </sqlMapConfig>

這樣在呼叫service之前只需要設定一下上下文即可呼叫相應的資料來源,如下:

Java程式碼 複製程式碼
  1. DbContextHolder.setDbType("2");   
  2. //execute services
  3. //.........

dao如下

Java程式碼 複製程式碼
  1. package com.xxx.xxx.dao.impl;   
  2. import java.util.HashMap;   
  3. import java.util.List;   
  4. import java.util.Map;   
  5. import org.apache.log4j.Logger;   
  6. import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;   
  7. import com.xxx.xxx.vo.TestObj;   
  8. publicclass TestDAO extends SqlMapClientDaoSupport implements ITestDAO {   
  9. static Logger log = Logger.getLogger(TestDAO.class);   
  10. public TestObj getTestObj(String objID) throws Exception {   
  11. return (TestObj) getSqlMapClientTemplate().queryForObject("getTestObj", objID);   
  12.     }   
  13. }  

 /******************************一 、複製來的例子 結束**********************************************/

/********************************************二、動態分配資料來源 spring2.5  +ibatis 開始*************************************************/ application中db的配置
<?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*************************************************/