同一個項目有時會涉及到多個數據庫,也就是多數據源。多數據源又可以分為兩種情況:
1)兩個或多個數據庫沒有相關性,各自獨立,其實這種可以作為兩個項目來開發。比如在游戲開發中一個數據庫是平臺數據庫,其它還有平臺下的游戲對應的數據庫;
2)兩個或多個數據庫是-slave的關系,比如有mysql搭建一個 -,其后又帶有多個slave;或者采用MHA搭建的-slave復制;
目前我所知道的 多數據源的搭建大概有兩種方式spring配置動態數據源,可以根據多數據源的情況進行選擇。
1. 采用配置文件直接配置多個數據源
比如針對兩個數據庫沒有相關性的情況,可以采用直接在的配置文件中配置多個數據源,然后分別進行事務的配置,如下所示:
mybatis.spring.SqlSessionFactoryBean">

如上所示,我們分別配置了兩個 ,兩個,兩個,以及關鍵的地方在于rer 的配置——使用nName屬性,注入不同的的名稱,這樣的話,就為不同的數據庫對應的 接口注入了對應的 。
需要注意的是,多個數據庫的這種配置是不支持分布式事務的,也就是同一個事務中,不能操作多個數據庫。這種配置方式的優點是很簡單,但是卻不靈活。對于-slave類型的多數據源配置而言不太適應,-slave性的多數據源的配置,需要特別靈活,需要根據業務的類型進行細致的配置。比如對于一些耗時特別大的語句,我們希望放到slave上執行,而對于,等操作肯定是只能在上執行的,另外對于一些實時性要求很高的語句,我們也可能需要放到上執行——比如一個場景是我去商城購買一件兵器,購買操作的很定是,同時購買完成之后,需要重新查詢出我所擁有的兵器和金幣,那么這個查詢可能也需要防止上執行spring配置動態數據源,而不能放在slave上去執行,因為slave上可能存在延時,我們可不希望玩家發現購買成功之后,在背包中卻找不到兵器的情況出現。
所以對于-slave類型的多數據源的配置,需要根據業務來進行靈活的配置,哪些可以放到slave上,哪些不能放到slave上。所以上面的那種所數據源的配置就不太適應了。
2. 基于 ource 和 AOP 的多數據源的配置
基本原理是,我們自己定義一個類,來繼承ource,然后在配置文件中向注入 和 slave 的數據源,然后通過 AOP 來靈活配置,在哪些地方選擇 數據源,在哪些地方需要選擇 slave數據源。下面看代碼實現:
1)先定義一個enum來表示不同的數據源:
package net.aazj.enums;
/**

* 數據源的類別:master/slave
*/
public enum DataSources {
MASTER, SLAVE
}
2)通過 來保存每個線程選擇哪個數據源的標志(key):
package net.aazj.util;
import net.aazj.enums.DataSources;
public class DataSourceTypeManager {
private static final ThreadLocal dataSourceTypes = new ThreadLocal(){
@Override
protected DataSources initialValue(){
return DataSources.MASTER;
}
};
public static DataSources get(){
return dataSourceTypes.get();
}
public static void set(DataSources dataSourceType){
dataSourceTypes.set(dataSourceType);
}
public static void reset(){
dataSourceTypes.set(DataSources.MASTER0);
}
}
3)定義 ,繼承ource:
package net.aazj.util;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
4)在配置文件中向 注入 和 slave 的數據源:


上面的配置文件中,我們針對數據庫和slave數據庫分別定義了和兩個,然后注入到中,這樣我們的就可以來根據 key 的不同來選擇和 了。
5)使用 AOP 來指定 的 key ,從而會根據key選擇 和 :
package net.aazj.aop;
import net.aazj.enums.DataSources;
import net.aazj.util.DataSourceTypeManager;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect // for aop
@Component // for auto scan
@Order? // execute before @Transactional
public class DataSourceInterceptor {
@Pointcut("execution(public * net.aazj.service..*.getUser(..))")
public void dataSourceSlave(){};
@Before("dataSourceSlave()")
public void before(JoinPoint jp) {
DataSourceTypeManager.set(DataSources.SLAVE);
}
// ... ...
}
這里我們定義了一個類,我們使用@來在符合@("( * net.aazj...*.(..))")中的方法被調用之前,調用r.set(.SLAVE)設置了 key 的類型為.SLAVE,所以 會根據key=.SLAVE選擇 這個。所以該方法對于的sql語句會在slave數據庫上執行,這里存在多個之間的一個執行順序的問題,必須保證切換數據源的必須在@這個之前執行,所以這里使用了@Order來保證切換數據源先于@執行)。
我們可以不斷的擴充r這個,在中進行各種各樣的定義,來為某個的某個方法指定合適的數據源對應的。
這樣我們就可以使用 AOP 的強大功能來,十分靈活進行配置了。
6)ource原理剖析
繼承了ource,實現其抽象方法 upKey(); 從而實現對不同數據源的路由功能。我們從源碼入手分析下其中原理:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
AbstractRoutingDataSource 實現了 InitializingBean 那么spring在初始化該bean時,會調用InitializingBean的接口
void afterPropertiesSet() throws Exception; 我們看下AbstractRoutingDataSource是如何實現這個接口的:
@Override

public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap
targetDataSources 是我們在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的
dataSourceMaster 和 dataSourceSlave來構造一個HashMap——resolvedDataSources。方便后面根據 key 從該map 中取得對應的dataSource。
我們在看下 AbstractDataSource 接口中的 Connection getConnection() throws SQLException; 是如何實現的:
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
關鍵在于ource(),根據方法名就可以看出,應該此處就決定了使用哪個 :
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;
}
Object lookupKey = determineCurrentLookupKey(); 該方法是我們實現的,在其中獲取ThreadLocal中保存的 key 值。獲得了key之后,
在從afterPropertiesSet()中初始化好了的resolvedDataSources這個map中獲得key對應的dataSource。而ThreadLocal中保存的 key 值
是通過AOP的方式在調用service中相關方法之前設置好的。OK,到此搞定!
7)擴展
上面我們只是實現了 -slave 數據源的選擇。如果有多臺 或者有多臺 slave。多臺組成一個HA,要實現當其中一臺掛了是,自動切換到另一臺,這個功能可以使用LVS/來實現,也可以通過進一步擴展來實現,可以另外加一個線程專門來每個一秒來測試mysql是否正常來實現。同樣對于多臺slave之間要實現負載均衡,同時當一臺slave掛了時,要實現將其從負載均衡中去除掉,這個功能既可以使用LVS/來實現,同樣也可以通過近一步擴展來實現。
3. 總結
從本文中我們可以體會到AOP的強大和靈活。
本文使用的是,其實使用也應該是相似的配置。
4.結尾
我不能保證寫的每個地方都是對的,但是至少能保證不復制、不黏貼,保證每一句話、每一行代碼都經過了認真的推敲、仔細的斟酌。每一篇文章的背后,希望都能看到自己對于技術、對于生活的態度,然后小編會持續更新的噢,喜歡的朋友可以關注下我。
我相信喬布斯說的,只有那些瘋狂到認為自己可以改變世界的人才能真正地改變世界。面對壓力,我可以挑燈夜戰、不眠不休;面對困難,我愿意迎難而上、永不退縮。
其實我想說的是,我只是一個程序員,這就是我現在純粹人生的全部。
最后希望大家多多支持 一鍵三連+評論下 并獻上我自己整理的“大廠真題+微服務+MySQL+分布式+SSM框架+Java+Redis+數據結構與算法+網絡+Linux+全家桶+JVM+高并發+各大學習思維腦圖+面試集合”