天天看點

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