使用spring的動態路由實現數據庫負載均衡

 在spring2.0.1發佈之前,各個項目中可能存在多種針對這種情況下的多數據源管理方式, 不過,spring2.0.1發佈之後,引入了AbstractRoutingDataSource,可以通過集成org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource類,自定義動態數據源
配置如下: datasource-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans  
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
http://www.springframework.org/schema/context  
 http://www.springframework.org/schema/context/spring-context-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">
	<!-- 數據源配置 -->
	<beanid="dataSourceFirst"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
		<propertyname="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
		<propertyname="url"value="jdbc:oracle:thin:@10.20.151.4:1521:ptdev"/>
		<propertyname="username"value="pt"/>
		<propertyname="password"value="pt"/>
		<propertyname="maxActive"value="200"/>
		<propertyname="maxIdle"value="5"/>
		<propertyname="poolPreparedStatements"value="true"/>
		<propertyname="removeAbandoned"value="true"/>
		<propertyname="removeAbandonedTimeout"value="300"/>
	</bean>
	<beanid="dataSourceSecond"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">
		<propertyname="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
		<propertyname="url"value="jdbc:oracle:thin:@10.20.151.12:1521:pt10g"/>
		<propertyname="username"value="pt"/>
		<propertyname="password"value="pt"/>
		<propertyname="maxActive"value="200"/>
		<propertyname="maxIdle"value="5"/>
		<propertyname="poolPreparedStatements"value="true"/>
		<propertyname="removeAbandoned"value="true"/>
		<propertyname="removeAbandonedTimeout"value="300"/>
	</bean>
	<beanid="dataSource"class="com.common.bean.RoutingDataSource">
		<propertyname="targetDataSources">
			<map>
				<entrykey="1"value-ref="dataSourceFirst"/>
				<entrykey="2"value-ref="dataSourceSecond"/>
			</map>
		</property>
		<propertyname="defaultTargetDataSource">
			<reflocal="dataSourceFirst"/>
		</property>
	</bean>
	<!--配置事物-->
	<beanid="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<propertyname="dataSource">
			<reflocal="dataSource"/>
		</property>
	</bean>
	<beanid="lobHandler"class="org.springframework.jdbc.support.lob.DefaultLobHandler"lazy-init="true"/>
	<beanid="sqlMapClient"class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<propertyname="dataSource"ref="dataSource"/>
		<propertyname="lobHandler"ref="lobHandler"/>
		<propertyname="configLocations"value="classpath*:/ibatis/config/sql-map.xml"/>
	</bean>
	<beanid="txAttributeSource"class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">
		<propertyname="properties">
			<props>
				<propkey="add*">PROPAGATION_REQUIRED,-PtServiceException</prop>
				<propkey="update*">PROPAGATION_REQUIRED,-PtServiceException</prop>
				<propkey="delete*">PROPAGATION_REQUIRED,-PtServiceException</prop>
				<propkey="batch*">PROPAGATION_REQUIRED,-PtServiceException</prop>
				<propkey="get*">PROPAGATION_REQUIRED,-PtServiceException</prop>
			</props>
		</property>
	</bean>
	<beanid="transactionManagerProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
		<propertyname="proxyTargetClass">
			<value>true</value>
		</property>
		<propertyname="target">
			<refbean="transactionManager"/>
		</property>
	</bean>
	<beanid="transactionDefinition"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"abstract="true">
		<propertyname="transactionManager">
			<refbean="transactionManagerProxy"/>
		</property>
		<propertyname="transactionAttributeSource">
			<refbean="txAttributeSource"/>
		</property>
	</bean>
	<beanid="baseDao"class="com.common.dao.impl.BaseDaoImpl">
		<propertyname="sqlMapClient">
			<refbean="sqlMapClient"/>
		</property>
		<propertyname="dataSource">
			<refbean="dataSource"/>
		</property>
	</bean>
	<beanid="dbInfoService"parent="transactionDefinition">
		<propertyname="target">
			<beanclass="com.service.impl.DbInfoServiceImpl">
				<propertyname="baseDao"ref="baseDao"/>
			</bean>
		</property>
	</bean>
	<beanid="test"class="com.common.bean.Test">
		<propertyname="dbInfoService"ref="dbInfoService">
		</property>
	</bean>
</beans>
com.common.bean.RoutingDataSource類:
package com.common.bean;import java.sql.Connection;import java.sql.SQLException;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;publicclassRoutingDataSourceextendsAbstractRoutingDataSource{protectedObject determineCurrentLookupKey(){//獲取當前線程處理的賬號對應分片信息Shard shard =ThreadInfoHolder.getCurrentThreadShard();//動態選定DataSourceString dbId = shard ==null?null:String.valueOf(shard.getDbId());return dbId;}@OverridepublicString toString(){//獲取當前線程處理的賬號對應分片信息Shard shard =ThreadInfoHolder.getCurrentThreadShard();//動態選定DataSourceString dbId = shard ==null?null:String.valueOf(shard.getDbId());return"DB ID "+ dbId +":"+super.toString();}publicString getTargetDbId()throwsSQLException{Connection conn =null;try{//jdbc:oracle:thin:@10.20.151.4:1521:ptdev, UserName=xx, Oracle JDBC driver
            conn = determineTargetDataSource().getConnection();if(conn !=null){String connectionDesc = conn.getMetaData().getURL();int beginIdx = connectionDesc.indexOf("@")+1;int endIdx = connectionDesc.indexOf(":", beginIdx);return connectionDesc.substring(beginIdx, endIdx);}}finally{if(conn !=null){
                conn.close();}}returnnull;}}

com.common.bean.ThreadInfoHolder類如下:package com.common.bean;publicclassThreadInfoHolder{// thread local, 獲取、存儲本線程處理的賬號對應分片信息privatestaticfinalThreadLocal<Shard> shardLocal =newThreadLocal<Shard>();/**
     * 獲取當前線程處理的賬號對應分片信息
     * 
     * @return
     */publicstaticShard getCurrentThreadShard(){returnThreadInfoHolder.shardLocal.get();}/**
     * 在當前線程存儲賬號對應分片信息
     * 
     * @param shard
     */publicstaticvoid addCurrentThreadShard(Shard shard){ThreadInfoHolder.shardLocal.set(shard);}/**
     * 清空前線程存儲分片信息
     * 
     * @param shard
     */publicstaticvoid cleanCurrentThreadShard(){ThreadInfoHolder.shardLocal.remove();}}
com.common.bean.Shard類如下:publicclassShard{//存放Account數據的DB_IDprivateInteger            dbId;/**
     * @return the dbId
     */publicInteger getDbId(){return dbId;}/**
     * @param dbId the dbId to set
     */publicvoid setDbId(Integer dbId){this.dbId = dbId;}}

com.service.DbInfoService接口:package com.service;import java.util.List;import java.util.Map;import com.common.dao.model.User;publicinterfaceDbInfoService{publicList<Map<String,Object>> getUserInfo(User user);}

com.service.impl.DbInfoServiceImpl實現類:package com.service.impl;import java.util.List;import java.util.Map;import com.common.bean.Shard;import com.common.bean.ThreadInfoHolder;import com.common.dao.BaseDao;import com.common.dao.model.User;import com.service.DbInfoService;publicclassDbInfoServiceImplimplementsDbInfoService{publicBaseDao baseDao;publicvoid setBaseDao(BaseDao baseDao){this.baseDao = baseDao;}publicList<Map<String,Object>> getUserInfo(User user){

        baseDao.add("login.addUser", user);List<Map<String,Object>> result=baseDao.getList("login.getUserInfo",user.getName());return result;}}

com.common.bean.Test類:package com.common.bean;import java.util.List;import java.util.Map;import com.common.dao.model.User;import com.service.DbInfoService;publicclassTest{publicDbInfoService dbInfoService;publicvoid setDbInfoService(DbInfoService dbInfoService){this.dbInfoService = dbInfoService;}publicList<Map<String,Object>> getInfo(User user){List<Map<String,Object>> result=dbInfoService.getUserInfo(user);return result;}}

測試main方法如下:

package com.transaction;import java.util.List;import java.util.Map;import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.core.io.ClassPathResource;import com.common.bean.Shard;import com.common.bean.Test;import com.common.bean.ThreadInfoHolder;import com.common.dao.model.User;import com.service.impl.DbInfoServiceImpl;publicclassTransactionTest{publicstaticvoid main(String[] args){ApplicationContext ctx =newClassPathXmlApplicationContext("classpath*:spring/datasource-config.xml");Test t=(Test)ctx.getBean("test");User user=newUser();
        user.setName("xj");
        user.setPassword("123");Shard shard=newShard();
        shard.setDbId(1);//使用datasource1ThreadInfoHolder.addCurrentThreadShard(shard);List<Map<String,Object>> result =t.getInfo(user);
        shard.setDbId(2);//使用datasource2ThreadInfoHolder.addCurrentThreadShard(shard);List<Map<String,Object>> result1 =t.getInfo(user);System.out.println(result);}}

運行結果是分別向數據庫1和數據庫2中插入了1調記錄。

注意:

由於對DbInfoService配置了事物,如果將切換數據源的代碼ThreadInfoHolder.addCurrentThreadShard(shard);放在在DbInfoServiceImpl類的getUserInfo方法中,如下:

publicList<Map<String,Object>> getUserInfo(User user){Shard shard=newShard();
    shard.setDbId(1);ThreadInfoHolder.addCurrentThreadShard(shard);
    baseDao.add("login.addUser", user);

    shard.setDbId(2);ThreadInfoHolder.addCurrentThreadShard(shard);
    baseDao.add("login.addUser", user);List<Map<String,Object>> result=baseDao.getList("login.getUserInfo",user.getName());return result;}

main方法改爲:

publicstaticvoid main(String[] args){ApplicationContext ctx =newClassPathXmlApplicationContext("classpath*:spring/datasource-config.xml");Test t=(Test)ctx.getBean("test");User user=newUser();
    user.setName("xj");
    user.setPassword("123");List<Map<String,Object>> result =t.getInfo(user);System.out.println(result);}

則運行結果將是向數據庫1中插入2條相同的記錄,而不是分別想數據庫1,2各插一條記錄,產生該結果的原因是應爲由於DbInfoServiceImpl配置了事物,所以在getUserInfo方法中的第一次連數據庫會新建一個連接,而後將該連接綁定在線程的本地變量即ThreadLoad中,當以後在需要訪問數據庫時不在新建連接而是使用這個綁定了老連接,在本例子中,即第一次的數據庫連接是連數據庫1,當第二次訪問數據庫時,使用的還是這個數據庫1的連接,即切換數據源設置代碼shard.setDbId(2);ThreadInfoHolder.addCurrentThreadShard(shard);失效。同理我們可以退出,如果將切換數據源代碼放在Test類的getInfo方法中,即:

publicList<Map<String,Object>> getInfo(User user){Shard shard=newShard();
    shard.setDbId(1);ThreadInfoHolder.addCurrentThreadShard(shard);List<Map<String,Object>> result=dbInfoService.getUserInfo(user);
    shard.setDbId(2);ThreadInfoHolder.addCurrentThreadShard(shard);List<Map<String,Object>> result1=dbInfoService.getUserInfo(user);return result;}

這樣是能正確運行的,應爲在調用事物前我們已經切換了數據源。


相關文章:


發佈了404 篇原創文章 · 獲贊 205 · 訪問量 227萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章