天天看点

Spring中使用AbstractRoutingDataSource实现多数据源的配置和使用中遇到的问题总结,另一种实现方案1.AbstractRoutingDataSource实现方式

1.AbstractRoutingDataSource实现方式

这种实现方式网上可以找到很多介绍。大同小异,我这里在总结一下。

1.1首先是数据源的配置

<!-- 有几个数据源就配几个 -->

<!-- 数据源配置1 -->  
<bean id="testDataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">   
  <property name="driverClassName" value="${db.driver}" />  
   <property name="url" value="${unity.db.jdbc.url}" />   
  <property name="username" value="${db.login.name}"></property>  
  <property name="password" value="${db.login.password}" />  
  ...等等配置信息...
</bean>  

<!-- 数据源配置2 -->  
<bean id="testDataSource2" class="com.alibaba.druid.pool.DruidDataSource"   init-method="init" destroy-method="close">   
    <property name="driverClassName" value="${db.driver}" />  
     <property name="url" value="${pub.db.jdbc.url}" />   
    <property name="username" value="${db.login.name}"></property>  
    <property name="password" value="${db.login.password}" />  
    ...等等配置信息...
</bean>  
<!-- 这里指定实现的动态数据源,配置默认数据源,配置key和对应数据源的对应关系 -->
<bean id="dataSource" class="com.xxx.dao.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="master" value-ref="testDataSource1"/>
            <entry key="dspinsight" value-ref="testDataSource2"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="master"/>
</bean>
           

1.2相关类的代码

动态数据源

public class DynamicDataSource extends AbstractRoutingDataSource {  
    @Override  
    protected Object determineCurrentLookupKey() {  
        return DataSourceContextHolder.getDataSourceType();  
    }  
}
           

存储当前线程的数据源的类

public class DataSourceContextHolder {  
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
    /** 
     * @Description: 设置数据源类型 
     * @param dataSourceType  数据库类型 
     * @return void 
     * @throws 
     */  
    public static void setDataSourceType(String dataSourceType) {  
        contextHolder.set(dataSourceType);  
    }  
      
    /** 
     * @Description: 获取数据源类型 
     * @param  
     * @return String 
     * @throws 
     */  
    public static String getDataSourceType() {  
        return contextHolder.get();  
    }  
      
    /** 
     * @Description: 清除数据源类型 
     * @param  
     * @return void 
     * @throws 
     */  
    public static void clearDataSourceType() {  
        contextHolder.remove();  
    }  
} 
           

在方法上指定数据源的注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}
           

切面的实现方式1,这种实现方式有个问题,就是方法中多次切换数据源会有问题。

@Component
@Aspect
@Order(1)
public class DataSourceAspect{

    private static Logger logger = LogFactory.log;

     // 这里是你的切面
    @Pointcut("execution(public * com.xxx.ad.service..*.*(..))")
    private void methodExec() {
    }

    @Before("methodExec()")
    private void before(JoinPoint point) throws NoSuchMethodException, SecurityException {
        Object target = point.getTarget();
        String method = point.getSignature().getName();

        Class<? extends Object> classz = target.getClass();

        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        Method m = classz.getMethod(method, parameterTypes);
        if (m != null && m.isAnnotationPresent(DataSource.class)) {
            DataSource data = m.getAnnotation(DataSource.class);
            String ds = data.value();
            logger.info("exchange to data source :" + ds);
            DynamicDataSourceHolder.setDataSource(ds);
        }
    }

    @After("methodExec()")
    private void after(JoinPoint point) {
        DynamicDataSourceHolder.clearDataSource();
    }
}
           

切面实现方式2,一个方法中可以切换多次数据源

@Aspect
@Order(1)
@Component
public class DynamicDataSourceAspect {

// 这里的
	@Around("@annotation(targetDataSource)")
	public Object Around(ProceedingJoinPoint pjp, DataSource targetDataSource) throws Throwable {
		String lastDbType = DataSourceContextHolder.getDBType();
		long tid = Thread.currentThread().getId();
		String methodName = "";
		Signature signature = pjp.getSignature();
		if(signature != null){
			methodName = signature.getName();
		}
		LogFactory.sailInfo("切换数据源,lastDbType:{},tid:{},methodName:{}", lastDbType,tid,methodName);
		String dataSourceKey = targetDataSource.value();
		DataSourceContextHolder.setDBType(dataSourceKey);
		LogFactory.sailInfo("设置数据源为  {},tid:{},methodName:{}", dataSourceKey,tid,methodName);
		
		Object ret = null;
		try {
			ret = pjp.proceed();
		}finally {
			LogFactory.sailInfo("执行方法完毕,当前数据源:{},tid:{},methodName:{}", DataSourceContextHolder.getDBType(),tid,methodName);
			
			LogFactory.sailInfo("恢复上次数据源:{},tid:{},methodName:{}", lastDbType,tid,methodName);
			DataSourceContextHolder.setDBType(lastDbType);
		}
		return ret;
	}
}

           

1.3使用方式

public class TestService ....{
	
	@DataSource("master")
	public void test1(){
		如果使用切面方式2实现,方法里面可以调用其他的数据库操作的服务
		testservice2.test2(); // 比如这个方法是操作其他的数据库
	}
}
           

2.使用中遇到的问题

2.1首先就是一个服务中存在多次切库的情况。按照切面1的实现方式,是存在问题的。如果使用around切面的话,可以解决这个问题。个人觉得事务可以加在Mapper上或者Dao上,不要加在Service。如果需要,在考虑加在Service。

2.2事务问题。在一个事务中,无法进行切库。原因的话,这里不解释了,网上很多介绍。解决办法的话,比较多的是使用了jta,我也没用过。大家搜索分布式事务吧。。。

2.3切库失败的问题。其切库失败的原因肯定不止一种,多数情况下都是由于事务造成的。参考2.2。我个人使用过程中发现过一个现象,就是程序运行一段时间之后,切库就莫名其妙的会失败。经过长时间的排查发现,是有同事的代码中手动开启了事务,但是事务会存在不提交的情况。。。还是和事务有关。

3.另一种实现方式参考

手动装配Mapper,参考我的另一篇博文

https://blog.csdn.net/lizhengwei1989/article/details/88081917