aop+AbstractRoutingDataSource實現數據庫讀寫分離、負載均衡

1,動態數據源類

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();//負載均衡,需要在這里進行從庫的key輪詢。 
    }
}

2,使用本地ThreadLocal,存儲當前線程的數據源。

threadlocal將具體的值保存在線程自身的threadLocalMap中

public abstract class DynamicDataSourceHolder {

    public static final String master = "master";

    public static final String slave = "slave";

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();//定義全局ThreadLocal對象,不同線程使用的是同一個對象。

    public static void setDbType(String dbTypeName){
        DynamicDataSourceHolder.threadLocal.set(dbTypeName);
    }

    public static String getDbType(){
        return DynamicDataSourceHolder.threadLocal.get();
    }

    public static void clearDbType(){
        DynamicDataSourceHolder.threadLocal.remove();
    }

}

3,配置多個數據源

    @Value("${datasource.username2}")
    private String username2;

    @Value("${datasource.password2}")
    private String password2;

    @Value("${datasource.url2}")
    private String url2;

    @Value("${datasource.username}")
    private String username;

    @Value("${datasource.password}")
    private String password;

    @Value("${datasource.url}")
    private String url;

    @Bean//配置主數據庫源
    public DataSource master(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return getDataSource(druidDataSource);
    }
   
   @Bean//配置從數據庫源
    public DataSource slave(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url2);
        druidDataSource.setUsername(username2);
        druidDataSource.setPassword(password2);
        return getDataSource(druidDataSource);
    }

    @Bean//配置動態數據源
    public DynamicDataSource dynamicDataSource(){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(master());//default數據源
        Map<Object, Object> map = Maps.newHashMap();
        map.put(DynamicDataSourceHolder.master, master());//key - value
        map.put(DynamicDataSourceHolder.slave, slave());
        dynamicDataSource.setTargetDataSources(map);
        return dynamicDataSource;
    }

4,自定義注解,標識使用哪個數據源

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface DynamicSourceAno {

    /**
     * 默認使用的數據源
     * @return
     */

    String value() default DynamicDataSourceHolder.master;

}

5,實現切面。

設置spring事務aop的執行順序@EnableTransactionManagement(order = 2) <tx:annotation-driven order="2"/>

@Aspect
@Component
@Order(1)//service層有@Transactional,事務也是aop實現的,這里定義order,要在事務開啟的aop執行前,進行數據源的切換,事務的aop結束后,進行數據源的恢復默認。
public class DynamicSourceAspect {

    private static Logger logger = LoggerFactory.getLogger(DynamicSourceAspect.class);

    @Before("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSourceAno)")
//在service的所有方法執行前,并且帶有注解DynamicSourceAno
    public void beforeDynamicSource(DynamicSourceAno dynamicSourceAno){

        try {
            DynamicDataSourceHolder.setDbType(dynamicSource.value());
        } catch (Throwable t) {
            logger.info(t.getMessage(), t);
        }

    }

    @After("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSource)")
    public void afterDynamicSource(DynamicSource dynamicSource){

        try {
            DynamicDataSourceHolder.clearDbType();
        } catch (Throwable t) {
            logger.info(t.getMessage(), t);
        }

    }

}

6,ThreadLocal與Thread

1)Thread類。定義了兩個TheadLocalMap類型屬性(可以用來保存對象),通過ThreadLocal類來維護的set,get,remove等操作

image.png

2)ThreadLocal類。ThreadLocalMap是ThreadLocal的靜態內部類。
Entry類中value屬性用來保存和線程關聯的具體對象,key是ThreadLocal類型
image.png

3)ThreadLocal類操作當前線程的ThreadLocalMap。

//保存value到當前線程
public void set(T value) {
        Thread t = Thread.currentThread();//得到當前線程
        ThreadLocalMap map = getMap(t);//得到當前線程的ThreadLocalMap對象。
        if (map != null)
            map.set(this, value);//將自身對象作為key。每個線程保存這個對象,都是使用相同的key,因為該類型的ThreadLocal對象只new了一個。
        else
            createMap(t, value);
}

//當第一次調用set方法時,會創建一個ThreadLocalMap對象。
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
//從當前線程的threadlocals中去除指定key對應的value
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//不同的線程使用相同的key來取值,得到的各自線程保存的對象。
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android Handler機制系列文章整體內容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 7,678評論 4 30
  • 前言 ThreadLocal很多同學都搞不懂是什么東西,可以用來干嘛。但面試時卻又經常問到,所以這次我和大家一起學...
    liangzzz閱讀 12,505評論 14 228
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,349評論 25 708
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,367評論 11 349
  • 我今年7月份畢業,最近正好在工作單位實習。很多大學生對畢業實習的態度就是四個字:應付了事。我覺得這是很可惜的一件事...
    葉泊菲閱讀 732評論 0 1