在搭建多数据源集成的时候主要注意两个方面:一个就是数据源切换的问题,另外一个就是事务的问题。

数据切换可以分为手工切换和aop自动切换。我们现在使用spring aop来自动切换。

首先配置一下数据源:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx"   
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:p="http://www.springframework.org/schema/p"  
    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.xsd  
       http://www.springframework.org/schema/context   
       http://www.springframework.org/schema/context/spring-context.xsd  
       http://www.springframework.org/schema/aop  
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/mvc      
       http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!--这里一定要注意,自动扫描整个包并把controller的扫描去掉,如果没有加上这个自动扫描,到时候会出现aop自动切换不成功-->
  
      <context:component-scan base-package="com.sclibackup">           
	 <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
    <!-- 引入数据库连接文件 -->
    <context:property-placeholder location="classpath*:properties/db.properties"/>
  
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!--  配置初始化大小、最小、最大 -->
	    <property name="initialSize" value="${mysql.initialSize}" />
	    <property name="minIdle" value="${mysql.minIdle}" /> 
	    <property name="maxActive" value="${mysql.maxActive}" />
	    <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${mysql.maxWait}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${mysql.timeBetweenEvictionRunsMillis}" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${mysql.minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="${mysql.validationQuery}" />
        <property name="testWhileIdle" value="${mysql.testWhileIdle}" />
        <property name="testOnBorrow" value="${myslq.testOnBorrow}" />
        <property name="testOnReturn" value="${mysql.testOnReturn}" />
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="${mysql.filters}" /> 
    </bean>

  
    <!-- 主库 -->  
    <bean name="master-dataSource" parent="dataSource"  init-method="init">  
    	<property name="driverClassName" value="${master.driverClassName}" />
        <property name="url" value="${master.url}" />  
        <property name="username" value="${master.username}" />  
        <property name="password" value="${master.password}" />  
    </bean>  
  
    <!-- 从库 -->  
    <bean name="slave-dataSource" parent="dataSource" init-method="init">
    	<property name="driverClassName" value="${slave.driverClassName}" />
        <property name="url" value="${slave.url}" />
        <property name="username" value="${slave.username}" />  
        <property name="password" value="${slave.password}" />  
    </bean>
      
    <!--主从库选择 -->  
    <bean id="dynamicDataSource" class="com.sclibackup.common.DynamicDataSource">  
        <property name="masterDataSource" ref="master-dataSource" />  
        <property name="slaveDataSources">  
            <list>  
                <ref bean="slave-dataSource" />  
            </list>  
        </property>  
    </bean>
    
    <!-- 整合mybatis -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource">
            <ref bean="dynamicDataSource"/>
        </property>
        <property name="configLocation">
            <value>classpath:config/spring-mybatis.xml</value>
        </property>
        <property name="mapperLocations">
            <value>classpath*:/mapping/*.xml</value>
        </property>
    </bean>
    
    <!-- <bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
	   <property name="mapperInterface" value="com.scli.dao.userDao" /> 
	   <property name="sqlSessionFactory" ref="sqlSessionFactory" /> 
	 </bean> -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.sclibackup.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>
    
    
    <!-- 配置事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource">
            <ref bean="dynamicDataSource"/>
        </property>
    </bean>
    
    <tx:advice id="txadvice" transaction-manager="transactionManager">
    	<tx:attributes>
    		<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    		<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    		<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    		<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    		<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    		<tx:method name="batch*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    	</tx:attributes>
    </tx:advice>
    
    
    <aop:config>
    	<aop:pointcut expression="execution(* com.***.service.impl.*.*(..))" id="adviced"/>
    	<aop:advisor advice-ref="txadvice" pointcut-ref="adviced"/>
    </aop:config>
	
</beans>

接着定义一个动态的数据源:

package com.***.common;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource{
	
	private DataSource masterDataSource;
	
	private List<DataSource> slaveDataSources;
	
	private final static String master = "master";
	
	private final static String slave = "slave";
	
	private Map<Object, Object> dataSources = new HashMap<Object, Object>();
	
	private int slaveSize = 0; 
	
	private AtomicLong slaveCount = new AtomicLong();

	
	//线程池使用LinkedList队列来存放线程,LinkedList API
	private static final ThreadLocal<LinkedList<String>> dataSourceHolder = new ThreadLocal<LinkedList<String>>(){

		@Override
		protected LinkedList<String> initialValue() {
			return new LinkedList<String>();
		}
		
	};

	@Override
	public void afterPropertiesSet() {
		if(masterDataSource==null){
			throw new IllegalArgumentException("Property 'master' is required");  
		}
		dataSources.put(master, masterDataSource);
		if(slaveDataSources!=null && slaveDataSources.size()>0){
			for(int i=0;i<slaveDataSources.size();i++){
				dataSources.put(slave+(i+1), slaveDataSources.get(i));
			}
			slaveSize = slaveDataSources.size();
		}
		this.setDefaultTargetDataSource(masterDataSource);  
        this.setTargetDataSources(dataSources);  
        super.afterPropertiesSet();  
	}
	
	/** 
     * 选择使用主库,并把选择放到当前ThreadLocal的栈顶 
     */
	public static void useMaster(){
		LinkedList<String> m = dataSourceHolder.get();  
        m.offerFirst(master);
	}
	
	/** 
     * 选择使用从库,并把选择放到当前ThreadLocal的栈顶 
     */
	public static void useSlave(){
		LinkedList<String> m = dataSourceHolder.get();  
        m.offerFirst(slave);
	}
	
	/** 
     * 重置当前栈 
     */ 
	public static void reset(){
		LinkedList<String> m = dataSourceHolder.get(); 
		if (m.size() > 0) {  
            m.poll();  
        }  
	}
	

	/** 
     * 如果是选择使用从库,且从库的数量大于1,则通过取模来控制从库的负载, 
     * 计算结果返回AbstractRoutingDataSource 
     */ 
	@Override
	protected Object determineCurrentLookupKey() {
		LinkedList<String> m = dataSourceHolder.get(); 
		String key = m.peekFirst() == null ? master : m.peekFirst();
		if(key!=null){
			if(key.equals(master)){
				return master;
			}
			if(key.equals(slave)){
				if(slaveSize>1){
					long c = slaveCount.incrementAndGet();  
                    c = c % slaveSize;  
                    return slave + (c + 1);
				}else{
					return slave+"1";
				}
			}
		}
		return null;
	}

	public DataSource getMasterDataSource() {
		return masterDataSource;
	}

	public void setMasterDataSource(DataSource masterDataSource) {
		this.masterDataSource = masterDataSource;
	}

	public List<DataSource> getSlaveDataSources() {
		return slaveDataSources;
	}

	public void setSlaveDataSources(List<DataSource> slaveDataSources) {
		this.slaveDataSources = slaveDataSources;
	}
	
}

接下来就是使用aop自动切换数据源:

自定义一个切换的注解,@interface就是自定义注解的标示:

package com.****.common;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

@Inherited  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME) 
public @interface DataSourceChange {

	boolean slave() default false; 
}

使用aop拦截来实现自动切换:

package com.****.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import com.sclibackup.common.DataSourceChange;
import com.sclibackup.common.DynamicDataSource;

@Aspect  
@Component
public class DataSourceAspect {
 //使用Around围绕拦截,通过@annotation加载自定义的注解
	@Around("@annotation(dataSourceChange)") 
	public Object doAround(ProceedingJoinPoint pjp, DataSourceChange dataSourceChange){
		Object retVal = null;  
        boolean selectedDataSource = false; 
        try {  
            if (null != dataSourceChange) {  
                selectedDataSource = true;  
                if (dataSourceChange.slave()) {  
                    DynamicDataSource.useSlave();  
                } else {  
                    DynamicDataSource.useMaster();  
                }  
            }  
            retVal = pjp.proceed();  
        } catch (Throwable e) {  
            e.printStackTrace(); 
        } finally {  
            if (selectedDataSource) {  
                DynamicDataSource.reset();  
            }  
        }  
        return retVal;
	}
}

数据源自动切换的工作已经准备完毕,接下来就是业务使用了:

public void run() {
				//从主数据库里面查出数据,再将数据插入到从服务器里面
				List<MypktHdr> list = orderService.GetMypktHdrDataByDate(tsd.getPreDate(), dd);
				for(MypktHdr hdr:list){
					orderService.SaveMypktHdr(hdr);
				}
			}
package com.****.service.impl;

import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.****.common.DataSourceChange;
import com.****.dao.MypktHdrMapper;
import com.****.model.MypktHdr;
import com.****.model.MypktHdrExample;
import com.****.model.MypktHdrExample.Criteria;
import com.****.service.IOrderService;

@Service
public class OrderServiceImpl implements IOrderService{
	

	@Autowired
	private MypktHdrMapper hdrMapper;

	@Override
	public List<MypktHdr> GetMypktHdrDataByDate(Date preDate, Date curDate) {
		MypktHdrExample me = new MypktHdrExample();
		Criteria ci = me.createCriteria();
		ci.andModifyDateGreaterThanOrEqualTo(preDate);
		ci.andModifyDateLessThanOrEqualTo(curDate);
		return hdrMapper.selectByExample(me);
	}

	@Override
	@DataSourceChange(slave = true)//在这里加上自定义的注解,把slave改成true就会去寻找从数据库了。
	public int SaveMypktHdr(MypktHdr hdr) {
		return hdrMapper.insertSelective(hdr);
	}
	
	
	
	
	
	
}

到这里就结束了。

但是里面有很多的知识点,就是如何实现数据源的切换:其中主要的还是继承了AbstractoutingDataSource,因为AbstractoutingDataSource继承了AbstractDataSource。而AbstracDataSource又实现了DataSource接口。所以AbstractoutingDataSource可以通过key来定义到某一个数据源。

spring 多数据源 指定xml目录 springmvc多数据源配置_xml


spring 多数据源 指定xml目录 springmvc多数据源配置_xml_02

既然是这样,那我们来看一个它是怎么连接的:

spring 多数据源 指定xml目录 springmvc多数据源配置_spring_03

连接是由determineTargetDataSource来确认的,然后由这个determineCurrentLookupKey决定使用哪个key。再通过resolvedDataSources来决定使用哪个数据源。

spring 多数据源 指定xml目录 springmvc多数据源配置_java_04