在搭建多数据源集成的时候主要注意两个方面:一个就是数据源切换的问题,另外一个就是事务的问题。
数据切换可以分为手工切换和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来定义到某一个数据源。
既然是这样,那我们来看一个它是怎么连接的:
连接是由determineTargetDataSource来确认的,然后由这个determineCurrentLookupKey决定使用哪个key。再通过resolvedDataSources来决定使用哪个数据源。