1. 程式人生 > >Springmvc+mybatis+spring多資料來源配置 AOP+註解方式切換資料來源

Springmvc+mybatis+spring多資料來源配置 AOP+註解方式切換資料來源

紙上得來終覺淺,絕知此事要躬行

最終效果就是,通過在service層類或方法上,註解宣告需要的資料來源名稱,從而實現切換資料來源的目的

理論知識就不說了,直接上程式碼(不支援分散式事務)*

1.準備多資料來源properties檔案

<!-- 資料庫1 -->
customer.jdbc.driver = com.mysql.jdbc.Driver
customer.jdbc.url = jdbc:mysql://localhost:3306/demo1?useUnicode=true&characterEncoding=utf-8
customer.jdbc.username = root
customer.jdbc.password = root
<!-- 資料庫2 -->
...

2.spring注入資料來源

<!-- 資料庫1 -->
<bean id="customer" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >  
        <property name="driverClass" value="#{database['customer.jdbc.driver']}" />  
        <property name="jdbcUrl" value="#{database['customer.jdbc.url']}"
/>
<property name="user" value="#{database['customer.jdbc.username']}" /> <property name="password" value="#{database['customer.jdbc.password']}"/> </bean> <!-- 資料庫2 --> ...

3.自定義DataSourceContextHolder類

public class DataSourceContextHolder {

    private
static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * @description 提供給AOP去設定當前的執行緒的資料來源的資訊 * @param dbType */ public static void setDbType(String dbType) { contextHolder.set(dbType); } /** * @description 提供給AbstractRoutingDataSource的實現類,通過key選擇資料來源 * @return */ public static String getDbType() { return (contextHolder.get()); } /** * @description 使用預設的資料來源 */ public static void clearDbType() { contextHolder.remove(); } }

4.自定義DynamicDataSource類繼承自AbstractRoutingDataSource抽象類並實現determineCurrentLookupKey方法

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

5.spring中注入多資料來源

<bean id="dynamicDataSource" class="com.core.DynamicDataSource" >  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry value-ref="customer" key="customer"></entry>  
                ...
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="customer">  
        </property>  
    </bean>

6.sqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource" />
        <property name="typeAliasesPackage" value="com.mapper1,com.mapper2" />
        <property name="mapperLocations">
            <list><!-- mapperLocations是陣列型別 list array都可以  示例為多路徑複雜情況 -->
                <value>classpath:/mybatis/*Mapper.xml</value>
                <value>classpath:/mybatis/base/*Mapper.xml</value>
            </list>
        </property>
    </bean>

7.sqlSessionTemplate通過sqlSessionFactory初始化

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

8.掃描所有mybatis的介面

<!-- 掃描basePaclage下所有mapper介面 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 若使用sqlSessionFactoryBeanName,則忽略上一步
                有小夥伴發現這個成員變數會導致資料庫切換失敗,我本人測試沒問題 
                個人覺得由於事務和資料來源的切換順序導致的問題居多
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
         -->
        <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate" />
        <property name="basePackage" value="com.dao1, com.dao2"></property>
    </bean>

9.自定義AOP動態切換資料來源切面類DataSourceAspect
方式1:採用註解方式,自定義註解做為切點標記
自定義註解DataSource

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

//使用自定義切換資料庫註解
@DataSource(value="customer")
@Service
public class CustomerService
/**
 * AOP動態切換資料來源切面類
 * @author user
 *
 */
@Component//<!-- 確認是否配置了該路徑下注釋配置bean的自動掃描 -->
@Order(0)//<!-- 設定切換資料來源的優先順序 -->
@Aspect
public class DataSourceAspect {


    //@within在類上設定
    //@annotation在方法上進行設定
    //事務在service層 因此切換資料庫的註解只能放在service層或之前
    @Pointcut("@within(com.core.DataSource)||@annotation(com.core.DataSource)")
    public void pointcut() {}


    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint)
    {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        DataSource annotationClass = method.getAnnotation(DataSource.class);
        if(annotationClass == null){
            annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            if(annotationClass == null) return;
        } 
        String dataSourceKey = annotationClass.value();
        if(dataSourceKey !=null){
            DatabaseContextHolder.setCustomerType(dataSourceKey);
        }  
    }

    @After("pointcut()")
    public void after(JoinPoint point) {

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        DatabaseContextHolder.clearCustomerType();
        System.out.println("切換預設資料來源成功!");
    }

}

注意:這裡有個坑,一定記得配置掃描器,確認已經存在的掃描路徑可以掃描到這個切面類

<!-- 採用註釋的方式配置bean -->
<context:component-scan base-package="com.core"></context:component-scan>
<!-- aop代理 true表示使用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

方式2:採用xml配置方式,切點表示式作為切點 首先還是定義切面類

public class DataSourceAspect {

    public void doBefore(JoinPoint joinPoint)
    {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        DataSource annotationClass = method.getAnnotation(DataSource.class);
        if(annotationClass == null){
            annotationClass = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
            if(annotationClass == null) return;
        } 
        String dataSourceKey = annotationClass.value();
        if(dataSourceKey !=null){
            DatabaseContextHolder.setCustomerType(dataSourceKey);
        }  
    } 
    public void after(JoinPoint point) {

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        DatabaseContextHolder.clearCustomerType();
        System.out.println("切換預設資料來源成功!");
    }

}

配置切面-切點-設定優先順序

<bean id="dataSourceAspect" class="com.core.DataSourceAspect"/>

<aop:config>
        <!--資料來源的切換應在事務之前,事務在service層,
        因此資料來源的切點至少在service層(或者controller層)-->
        <aop:pointcut id="switchDataSource" expression="execution(* com.service..*.*(..)) "/>
        <!--order的數值越小優先順序越高 -->
        <aop:aspect ref="dataSourceAspect" order="0">
            <aop:before method="doBefore" pointcut-ref="switchDataSource"/>
            <aop:after method="after" pointcut-ref="switchDataSource"/>
        </aop:aspect>   
    </aop:config>

設定事務的優先順序

<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" order="1"/>