使用spring的动态路由实现数据库负载均衡

27 篇文章 1 订阅
 在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;}

这样是能正确运行的,应为在调用事物前我们已经切换了数据源。


相关文章:


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值