AOP實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換 AbstractRoutingDataSource

原文:https://blog.csdn.net/u012881904/article/details/77449710

AbstractRoutingDataSource動(dòng)態(tài)數(shù)據(jù)源切換
上周末,室友通宵達(dá)旦的敲代碼處理他的多數(shù)據(jù)源的問(wèn)題,搞的非常的緊張,也和我聊了聊天,大概的了解了他的業(yè)務(wù)的需求。一般的情況下我們都是使用SSH或者SSM框架進(jìn)行處理我們的數(shù)據(jù)源的信息。
操作數(shù)據(jù)一般都是在DAO層進(jìn)行處理,可以選擇直接使用JDBC進(jìn)行編程(http://blog.csdn.net/yanzi1225627/article/details/26950615/
或者是使用多個(gè)DataSource 然后創(chuàng)建多個(gè)SessionFactory,在使用Dao層的時(shí)候通過(guò)不同的SessionFactory進(jìn)行處理,不過(guò)這樣的入侵性比較明顯,一般的情況下我們都是使用繼承HibernateSupportDao進(jìn)行封裝了的處理,如果多個(gè)SessionFactory這樣處理就是比較的麻煩了,修改的地方估計(jì)也是蠻多的
最后一個(gè),也就是使用AbstractRoutingDataSource的實(shí)現(xiàn)類通過(guò)AOP或者手動(dòng)處理實(shí)現(xiàn)動(dòng)態(tài)的使用我們的數(shù)據(jù)源,這樣的入侵性較低,非常好的滿足使用的需求。比如我們希望對(duì)于讀寫分離或者其他的數(shù)據(jù)同步的業(yè)務(wù)場(chǎng)景

單數(shù)據(jù)源的場(chǎng)景(一般的Web項(xiàng)目工程這樣配置進(jìn)行處理,就已經(jīng)比較能夠滿足我們的業(yè)務(wù)需求)

多數(shù)據(jù)源多SessionFactory這樣的場(chǎng)景,估計(jì)作為剛剛開始想象想處理在使用框架的情況下處理業(yè)務(wù),配置多個(gè)SessionFactory,然后在Dao層中對(duì)于特定的請(qǐng)求,通過(guò)特定的SessionFactory即可處理實(shí)現(xiàn)這樣的業(yè)務(wù)需求,不過(guò)這樣的處理帶來(lái)了很多的不便之處,所有很多情況下我們寧愿直接使用封裝的JDBC編程,或者使用Mybatis處理這樣的業(yè)務(wù)場(chǎng)景
使用AbstractRoutingDataSource 的實(shí)現(xiàn)類,進(jìn)行靈活的切換,可以通過(guò)AOP或者手動(dòng)編程設(shè)置當(dāng)前的DataSource,不用修改我們編寫的對(duì)于繼承HibernateSupportDao的實(shí)現(xiàn)類的修改,這樣的編寫方式比較好,至于其中的實(shí)現(xiàn)原理,讓我細(xì)細(xì)到來(lái)。我們想看看如何去應(yīng)用,實(shí)現(xiàn)原理慢慢的說(shuō)!

編寫AbstractRoutingDataSource的實(shí)現(xiàn)類,HandlerDataSource就是提供給我們動(dòng)態(tài)選擇數(shù)據(jù)源的數(shù)據(jù)的信息,我們這里編寫一個(gè)根據(jù)當(dāng)前線程來(lái)選擇數(shù)據(jù)源,然后通過(guò)AOP攔截特定的注解,設(shè)置當(dāng)前的數(shù)據(jù)源信息,也可以手動(dòng)的設(shè)置當(dāng)前的數(shù)據(jù)源,在編程的類中。

package com.common.utils.manydatasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * descrption: 多數(shù)據(jù)源的選擇
 * authohr: wangji
 * date: 2017-08-21 10:32
 */
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {

    /**
     * @desction: 根據(jù)Key獲取數(shù)據(jù)源的信息,上層抽象函數(shù)的鉤子
     * @author: wangji
     * @date: 2017/8/21
     * @param:
     * @return:
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return HandlerDataSource.getDataSource();
    }
}

設(shè)置動(dòng)態(tài)選擇的Datasource,這里的Set方法可以留給AOP調(diào)用,或者留給我們的具體的Dao層或者Service層中手動(dòng)調(diào)用,在執(zhí)行SQL語(yǔ)句之前。

package com.common.utils.manydatasource;

/**
 * descrption: 根據(jù)當(dāng)前線程來(lái)選擇具體的數(shù)據(jù)源
 * authohr: wangji
 * date: 2017-08-21 10:36
 */
public class HandlerDataSource {

    private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>();

    /**
     * @desction: 提供給AOP去設(shè)置當(dāng)前的線程的數(shù)據(jù)源的信息
     * @author: wangji
     * @date: 2017/8/21
     * @param: [datasource]
     * @return: void
     */
    public static void putDataSource(String datasource) {
        handlerThredLocal.set(datasource);
    }

    /**
     * @desction: 提供給AbstractRoutingDataSource的實(shí)現(xiàn)類,通過(guò)key選擇數(shù)據(jù)源
     * @author: wangji
     * @date: 2017/8/21
     * @param: []
     * @return: java.lang.String
     */
    public static String getDataSource() {
        return handlerThredLocal.get();
    }

    /**
     * @desction: 使用默認(rèn)的數(shù)據(jù)源
     */
    public static void clear() {
        handlerThredLocal.remove();
    }
}

設(shè)置攔截?cái)?shù)據(jù)源的注解,可以設(shè)置在具體的類上,或者在具體的方法上,dataSource是當(dāng)前數(shù)據(jù)源的一個(gè)別名用于標(biāo)識(shí)我們的數(shù)據(jù)源的信息。

package com.common.utils.manydatasource;

import java.lang.annotation.*;

/**
 * @description: 創(chuàng)建攔截設(shè)置數(shù)據(jù)源的注解
 * Created by wangji on 2017/8/21.
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {

    String dataSource() default "";
}

AOP攔截類的實(shí)現(xiàn),通過(guò)攔截上面的注解,在其執(zhí)行之前處理設(shè)置當(dāng)前執(zhí)行SQL的數(shù)據(jù)源的信息,HandlerDataSource.putDataSource(….),這里的數(shù)據(jù)源信息從我們?cè)O(shè)置的注解上面獲取信息,如果沒有設(shè)置就是用默認(rèn)的數(shù)據(jù)源的信息。

package com.common.utils.manydatasource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * descrption: 使用AOP攔截特定的注解去動(dòng)態(tài)的切換數(shù)據(jù)源
 * authohr: wangji
 * date: 2017-08-21 10:42
 */
@Aspect
@Slf4j
@Component
@Order(1)
public class HandlerDataSourceAop {
    //@within在類上設(shè)置
    //@annotation在方法上進(jìn)行設(shè)置
    @Pointcut("@within(com.common.utils.manydatasource.DynamicSwitchDataSource)||@annotation(com.common.utils.manydatasource.DynamicSwitchDataSource)")
    public void pointcut() {}

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint)
    {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);//獲取方法上的注解
        if(annotationClass == null){
            annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//獲取類上面的注解
            if(annotationClass == null) return;
        }
        //獲取注解上的數(shù)據(jù)源的值的信息
        String dataSourceKey = annotationClass.dataSource();
        if(dataSourceKey !=null){
            //給當(dāng)前的執(zhí)行SQL的操作設(shè)置特殊的數(shù)據(jù)源的信息
            HandlerDataSource.putDataSource(dataSourceKey);
        }
        log.info("AOP動(dòng)態(tài)切換數(shù)據(jù)源,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默認(rèn)數(shù)據(jù)源":dataSourceKey);
    }

    @After("pointcut()")
    public void after(JoinPoint point) {
        //清理掉當(dāng)前設(shè)置的數(shù)據(jù)源,讓默認(rèn)的數(shù)據(jù)源不受影響
        HandlerDataSource.clear();
    }

}

配置數(shù)據(jù)源在Spring 核心容器中配置

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis
jdbc.username=root
jdbc.password=root
jdbc2.url=jdbc:mysql://127.0.0.1:3306/datasource2

<!-- 配置數(shù)據(jù)源 -->
    <bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="10"/>
    </bean>
    <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc2.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="10"/>
    </bean>

配置之前我們實(shí)現(xiàn)的數(shù)據(jù)源選擇的中間層AbstractRoutingDataSource的實(shí)現(xiàn)類,這里的key就是數(shù)據(jù)源信息的別名,通過(guò)這個(gè)key可以選擇到數(shù)據(jù)源的信息。MultipleDataSourceToChoose就是上面寫的數(shù)據(jù)源選擇器的實(shí)現(xiàn)類

bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
        <description>數(shù)據(jù)源</description>
        <property name="targetDataSources">
            <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                <entry key="datasource0" value-ref="dataSource0" />
                <entry key="datasource1" value-ref="dataSource1" />
            </map>
        </property>
        <!-- 設(shè)置默認(rèn)的目標(biāo)數(shù)據(jù)源 -->
        <property name="defaultTargetDataSource" ref="dataSource0" />
    </bean>

SessionFactory的配置還是照舊,使用以前的配置,只不過(guò)當(dāng)前選擇的數(shù)據(jù)源是datasource,也就是數(shù)據(jù)源選擇的中間層MultipleDataSourceToChoose,因?yàn)楫?dāng)前的中間層中實(shí)現(xiàn)了DataSource這個(gè)接口,所以可以看做為DataSource的是實(shí)現(xiàn)類啦,所以配置不會(huì)出現(xiàn)問(wèn)題。

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--指定Hibernate屬性 -->
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">false</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.autoReconnect">true</prop>
                <prop key="hibernate.jdbc.batch_size">50</prop>
              <prop key="hibernate.connection.autocommit">false</prop>
              <prop key="hibernate.connection.release_mode">after_transaction</prop>
              <prop key="hibernate.bytecode.use_reflection_optimizer">false</prop>
            </props>
        </property>
        <property name="packagesToScan">
            <list>
                <value>com.module</value>
            </list>
        </property>
    </bean>

簡(jiǎn)單的使用AOP進(jìn)行測(cè)試一下,這里測(cè)試的結(jié)果時(shí)不同的,所以是生效的,使用了不同的數(shù)據(jù)源,但是底層的實(shí)現(xiàn)沒有進(jìn)行任何的修改處理。

@Service
@Slf4j
public class UserInfoService implements IUserInfoService {


    @Resource
    private UserDao userDao;
    @Autowired
    private CommonHibernateDao commonDao;

    @TestValidateParam
    public User getUserInfoById(Integer id) {
        return userDao.findById(id);
    }

    @DynamicSwitchDataSource(dataSource = "datasource0")
    public void save(User user) {
        userDao.save(user);
    }

    @DynamicSwitchDataSource(dataSource = "datasource1")
    public List<User> findAll(){
        String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
        List<User> list =commonDao.findListBySQL(sql,User.class);
        return list;
    }

}

也可以不適用AOP,直接在編程中實(shí)現(xiàn),通過(guò)測(cè)試,結(jié)果分別為兩個(gè)數(shù)據(jù)庫(kù)中的信息

 public void test(){
        HandlerDataSource.putDataSource("datasource1");
        String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
        List<User> list =commonDao.findListBySQL(sql,User.class);

        HandlerDataSource.putDataSource("datasource0");
        commonDao.deleteById("2",User.class);
    }

實(shí)現(xiàn)原理,MultipleDataSourceToChoose的繼承結(jié)構(gòu)圖,之前說(shuō)過(guò)他是DataSource的子類,由于無(wú)論我們是使用Mybatis還是使用Hibernate進(jìn)行SQL操作的時(shí)候總會(huì)執(zhí)行g(shù)etConnection(),無(wú)論我們的數(shù)據(jù)源是否使用了數(shù)據(jù)庫(kù)連接池,因?yàn)閿?shù)據(jù)庫(kù)連接池的主要作用就是保持一堆的Connection不進(jìn)行關(guān)閉的處理,節(jié)省我們的關(guān)閉和打開連接的開銷。http://blog.csdn.net/shuaihj/article/details/14223015/ 淺談數(shù)據(jù)庫(kù)連接池說(shuō)的簡(jiǎn)單易懂。 Connection getConnection() throws SQLException;所以這句話總是要執(zhí)行的,只是AbstractRoutingDataSource這個(gè)類給我們進(jìn)行了一些中介的處理,在獲取Connection的時(shí)候會(huì)去尋找保存的DataSource的引用,到底是選擇哪個(gè)DataSource進(jìn)行處理,看代碼!

配置的參數(shù)

<bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
        <description>數(shù)據(jù)源</description>
        <property name="targetDataSources">
            <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                <entry key="datasource0" value-ref="dataSource0" />
                <entry key="datasource1" value-ref="dataSource1" />
            </map>
        </property>
        <!-- 設(shè)置默認(rèn)的目標(biāo)數(shù)據(jù)源 -->
        <property name="defaultTargetDataSource" ref="dataSource0" />
    </bean>

targetDataSources,是一個(gè)Map對(duì)于數(shù)據(jù)源的引用

public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }

對(duì)于實(shí)現(xiàn)SQL的Connection getConnection() throws SQLException的實(shí)現(xiàn),其實(shí)就是代理模式找到之前Map的引用,通過(guò)key,而這個(gè)key就是我們靈活配置的key,通過(guò)這個(gè)key就可以尋找到這個(gè)值。

public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

這里說(shuō)的非常的詳細(xì),通過(guò)鉤子函數(shù)讓子類去實(shí)現(xiàn),尋找特定的key,然后選擇DataSource 的時(shí)候就可以很靈活的使用啦!

/**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

這個(gè)就是模板方法模式中常見的鉤子函數(shù),在HttpServlet中也有類似的使用鉤子,非常的棒,不過(guò)這個(gè)是必須實(shí)現(xiàn),httpServlet不是必須實(shí)現(xiàn),只是添加一些補(bǔ)充。由于每次執(zhí)行數(shù)據(jù)庫(kù)的調(diào)用,總會(huì)執(zhí)行這個(gè)getConnection方法,每次都查看AOP中是否設(shè)置了當(dāng)前的數(shù)據(jù)源,然后找到Map的引用的代理的數(shù)據(jù)源的Connection方法,原理沒有變化的。

/**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    protected abstract Object determineCurrentLookupKey();

    
這里就是我們的實(shí)現(xiàn)的數(shù)據(jù)源的選擇哦!  
    
/**
 * descrption: 多數(shù)據(jù)源的選擇
 * authohr: wangji
 * date: 2017-08-21 10:32
 */
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {

    /**
     * @desction: 根據(jù)Key獲取數(shù)據(jù)源的信息,上層抽象函數(shù)的鉤子
     * @author: wangji
     * @date: 2017/8/21
     * @param:
     * @return:
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return HandlerDataSource.getDataSource();
    }
}

但是這個(gè)由于有多個(gè)數(shù)據(jù)源導(dǎo)致我們只能管理默認(rèn)的數(shù)據(jù)源的事務(wù)!
http://blog.csdn.net/erixhao/article/details/52133153/
https://github.com/WangJi92/mybatits-study/blob/master/mybatis-study/study-7-spring-Hibernate%20-manydatasource/src/main/java/com/common/utils/manydatasource/MultipleDataSourceToChoose.java/ 源碼地址

同類文章
SpringBoot + MyBatis + MySQL 讀寫分離實(shí)戰(zhàn)
http://www.itdecent.cn/p/8904af2c029a

AOP實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換 AbstractRoutingDataSource
http://www.itdecent.cn/p/cd99b94fe9de

spingboot 中通過(guò) DynamicDataSource來(lái)動(dòng)態(tài)獲取數(shù)據(jù)源
http://www.itdecent.cn/p/b2f818b742a2

AOP注解
https://my.oschina.net/u/2474629/blog/1083448

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,697評(píng)論 18 399
  • ?著作權(quán)歸作者所有:來(lái)自51CTO博客作者優(yōu)秀android的原創(chuàng)作品,如需轉(zhuǎn)載,請(qǐng)注明出處,否則將追究法律責(zé)任 ...
    傳奇內(nèi)服號(hào)閱讀 1,202評(píng)論 0 9
  • 在職場(chǎng)上,“責(zé)任心”是一個(gè)高頻詞匯。不管什么崗位的職位說(shuō)明中,大概少不了“認(rèn)真負(fù)責(zé)”之類的字眼。 也正是因?yàn)槿绱耍?..
    共同體GTT閱讀 871評(píng)論 0 1
  • 昨日小師妹WX與我約著出門吃完潮汕海鮮粥,從漢口路拐彎一直行走至河南路口,她驚呼手機(jī)丟了。派出所做完筆錄已是凌晨一...
    歡喜荼迷閱讀 203評(píng)論 0 0

友情鏈接更多精彩內(nèi)容