spring動態資料來源配置以及以及利用AOP自動設定
阿新 • • 發佈:2019-01-06
這個問題其實網上有很多的解決辦法。但是我在借鑑的時候,還是碰到了很多問題,有很多地方不明白。最後經過綜合參考幾篇博文,自己測試實驗,終於把問題解決了。在這裡記錄下來,避免以後我或者大家再遇到這樣的問題。
我主要參考的文章有:
1、Spring(AbstractRoutingDataSource)實現動態資料來源切換 這篇文章講的比較詳細,但是最後那個dataSourceExchange類沒有提供,導致我剛開始不明白這個是幹什麼用的,在不加這個類的情況下,使用事務的時候切換資料來源會不起作用,因為在函式內手動設定資料來源時,這時已經晚了。因為事務已經建立,事務的資料來源已經獲取了。此時該函式還沒有執行,所以會導致事務獲取的是預設的資料來源。所以需要在事務獲取資料來源之間切換設定資料來源。這個就是dataSourceExchange類的作用。它是一個Aspect類,用來實現在函式執行前和執行後注入。
2、Spring 配置多個數據源,並實現動態切換 這篇文章裡提供了dataSourceExchange類。即裡面的DataSourceAspect類。
經過綜合這三篇文章,最終形成了自己的解決方法。
DynamicDataSource類程式碼如下:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
DataSourceContextHolder類程式碼如下:
public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * @Description: 設定資料來源型別 * @param dataSource 資料來源名稱 * @return void * @throws */ public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } /** * @Description: 獲取資料來源名稱 * @param * @return String * @throws */ public static String getDataSource() { return contextHolder.get(); } /** * @Description: 清除資料來源名稱 * @param * @return void * @throws */ public static void clearDataSource() { contextHolder.remove(); } }
DataSourceExchange類程式碼如下:
public class DataSourceExchange implements MethodBeforeAdvice,AfterReturningAdvice
{
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
DataSourceContextHolder.clearDataSource();
}
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
//這裡DataSource是自定義的註解,不是java裡的DataSource介面
if (method.isAnnotationPresent(DataSource.class))
{
DataSource datasource = method.getAnnotation(DataSource.class);
DataSourceContextHolder.setDataSourceType(datasource.name());
}
else
{
//target是被織入增強處理的目標物件,通過獲取getDataSourceName函式來獲取target的資料來源名稱
DataSourceContextHolder.setDataSource(
target.getClass.getMethod("getDataSourceName").invoke(target).toString());
}
}
}
從這段程式碼可以看出,織入方式支援兩種,一種是註解方式,一種就是配置方式。對於註解方式,下面提供DataSource自定義註解類,在每個需要更改資料來源的函式上面加上DataSource註解即可。第二種方式是配置方式,對於這種方式,從程式碼中可以看出,被織入的函式所在的類需要提供一個getDataSourceName的函式,該函式返回該類需要的資料來源名稱。這裡可以根據自己實際的情況進行更改。因為我的每個Service類都是相同的資料來源,所以我在每個service類中提供了一個getDataSourceName的函式。
DataSource註解程式碼如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "defaultSource";
}
資料來源配置的相關片段如下,我的系統中是spring和hibernate整合使用的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="false">
<!-- Note: different database must match differnet context base file, for example: Oracle-context-base.xml -->
<!-- ========================= GENERAL DEFINITIONS ========================= -->
<!-- Configurer that replaces ${...} placeholders with values from properties files -->
<!-- (in this case, mail and JDBC related properties) -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="order" value="1" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath*:resources/param/cetc-*.properties</value>
</list>
</property>
</bean>
<!-- Datasource for JDBC or JNDI, switch by value of jndi.enabled -->
<bean id="defaultSource" init-method="init" destroy-method="close"
class="com.cetc.datamc.kernel.core.datasource.BasicDataSourceFactoryBean">
<property name="dataSourceClass" value="org.apache.commons.dbcp.BasicDataSource" />
<property name="configProperties">
<props>
<prop key="driverClassName">${jdbc.driverClassName}</prop>
<prop key="url">${jdbc.url}</prop>
<prop key="username">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="jndiName">${jndi.name}</prop>
</props>
</property>
<property name="jndiEnabled">
<value>${jndi.enabled}</value>
</property>
</bean>
<bean id="jcsjSource" init-method="init" destroy-method="close"
class="com.cetc.datamc.kernel.core.datasource.BasicDataSourceFactoryBean">
<property name="dataSourceClass" value="org.apache.commons.dbcp.BasicDataSource" />
<property name="configProperties">
<props>
<prop key="driverClassName">${jdbc.driverClassName}</prop>
<prop key="url">${jdbc.url}</prop>
<prop key="username">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="jndiName">${jndi.name}</prop>
</props>
</property>
<property name="jndiEnabled">
<value>${jndi.enabled}</value>
</property>
</bean>
<!-- 配置多資料來源對映關係 -->
<bean id="dataSource" class="com.cetc.datamc.kernel.core.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultSource" value-ref="defaultSource"></entry>
<entry key="jcsjSource" value-ref="jcsjSource"></entry>
</map>
</property>
<!-- 預設目標資料來源為你主庫資料來源 -->
<property name="defaultTargetDataSource" ref="defaultSource"/>
</bean>
<bean id="dataSourceExchange" class="com.cetc.datamc.kernel.core.datasource.DataSourceExchange"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<!-- <prop key="hibernate.jdbc.fetch_size">100</prop> --><!--由於oracle驅動的bug導致memory leak-->
<prop key="hibernate.generate_statistics">true</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="lobHandler">
<ref bean="oracleLobHandler" />
</property>
</bean>
<bean id="txManagerHibernate"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
</beans>
相關的aop配置片段如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!--FEXME 移至core工程-->
<!--
Spring 事務:
1、如果配置了HibernateTransactionManager,就不用DataSourceTransactionManager了
2、所有需要管理事務的類必須通過spring context管理。直接new的物件不行。
3、所有需要事務管理的類必須是interface,因為代理
-->
<tx:advice id="txAdviceHibernate" transaction-manager="txManagerHibernate">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception" no-rollback-for="" />
<tx:method name="adjust*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="assign*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="batch*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="cancel*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="change*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="create*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="execute*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="export*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="import*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="insert*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="go*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="modify*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="notify*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="recover*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="release*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="rename*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="reset*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="run*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="set*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="subscribe*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="swap*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="trigger*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception" />
<tx:method name="all*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="count*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="list*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="load*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="page*" propagation="NOT_SUPPORTED" read-only="true" />
<tx:method name="query*" propagation="NOT_SUPPORTED" read-only="true" />
</tx:attributes>
</tx:advice>
<!--此處應想辦法實現動態載入-->
<aop:config>
<aop:pointcut id="service" expression="execution(* com.cetc.datamc.app.collect.bs.*.*(..)) or execution(* com.cetc.datamc.app.mdms.bs.*.*(..))" />
<aop:advisor pointcut-ref="service" advice-ref="txAdviceHibernate" order="2" />
<aop:advisor pointcut-ref="service" advice-ref="dataSourceExchange" order="1" />
</aop:config>
</beans>
這樣所有相關的配置基本上就完整了,可以完全靈活的實現動態資料來源切換了