一、基礎介紹
多資料來源字面意思,比如說二個資料庫,甚至不同型別的資料庫。在用SpringBoot開發專案時,隨著業務量的擴大,我們通常會進行資料庫拆分或是引入其他資料庫,從而我們需要配置多個資料來源。
二、專案目錄截圖
三、多資料來源SQL結構設計如下(簡單的主從關係):
PS:建立兩個庫用於搭建專案中主從使用不同的資料庫,表可以隨意定義。
四、配置編碼
1.資料來源自定義註解,DataSource.java
/** * 資料來源自定義註解 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { DataSourcesType name() default DataSourcesType.MASTER; }
2.資料來源型別列舉類定義,DataSourcesType.java
/** * 資料來源型別 */ public enum DataSourcesType { /** * 主庫 */ MASTER, /** * 從庫 */ SLAVE }
3.多資料來源application.yml配置檔案配置
# 資料來源配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver druid: master: url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 slave: enable: true url: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 # 初始連線數 initialSize: 5 # 最小連線池數量 minIdle: 10 # 最大連線池數量 maxActive: 20 # 配置獲取連線等待超時的時間 maxWait: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一個連線在池中最小生存的時間,單位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一個連線在池中最大生存的時間,單位是毫秒 maxEvictableIdleTimeMillis: 900000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 開啟PSCache,並且指定每個連線上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆,此處是filter修改的地方 filters: commons-log.connection-logger-name: stat,wall,log4j # 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合併多個DruidDataSource的監控資料 useGlobalDataSourceStat: true # 配置 DruidStatFilter web-stat-filter: enabled: true url-pattern: /* exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* stat-view-servlet: enabled: true url-pattern: /druid/* # IP 白名單,沒有配置或者為空,則允許所有訪問 allow: 127.0.0.1 # IP 黑名單,若白名單也存在,則優先使用 deny: 192.168.31.253 # 禁用 HTML 中 Reset All 按鈕 reset-enable: false # 登入使用者名稱/密碼 login-username: root login-password: 123 # 慢SQL記錄 filter: stat: enabled: true # 慢SQL記錄 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true
4.資料來源配置檔案屬性定義,DataSourceProperties.java
/** * 資料來源配置檔案 */ @Setter @Configuration @ConfigurationProperties(prefix = "spring.datasource.druid") public class DataSourceProperties { private int initialSize; private int minIdle; private int maxActive; private int maxWait; private int timeBetweenEvictionRunsMillis; private int minEvictableIdleTimeMillis; private int maxEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; public DruidDataSource setDataSource(DruidDataSource datasource) { datasource.setInitialSize(initialSize); /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置獲取連線等待超時的時間 */ datasource.setMaxWait(maxWait); /** 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一個連線在池中最小、最大生存的時間,單位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用來檢測連線是否有效的sql,要求是一個查詢語句,常用select 'x'。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會起作用。 */ datasource.setValidationQuery(validationQuery); /** 建議配置為true,不影響效能,並且保證安全性。申請連線的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連線是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申請連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 歸還連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能。 */ datasource.setTestOnReturn(testOnReturn); return datasource; }
5.多資料來源切換處理,DynamicDataSourceContextHolder.java
/** * 資料來源切換處理 */ public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** *此類提供執行緒區域性變數。這些變數不同於它們的正常對應關係是每個執行緒訪問一個執行緒(通過get、set方法),有自己的獨立初始化變數的副本。 */ private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); /** * 設定當前執行緒的資料來源變數 */ public static void setDataSourceType(String dataSourceType) { log.info("已切換到{}資料來源", dataSourceType); contextHolder.set(dataSourceType); } /** * 獲取當前執行緒的資料來源變數 */ public static String getDataSourceType() { return contextHolder.get(); } /** * 刪除與當前執行緒繫結的資料來源變數 */ public static void removeDataSourceType() { contextHolder.remove(); } }
6.獲取資料來源(依賴於 spring) 定義一個類繼承AbstractRoutingDataSource實現determineCurrentLookupKey方法,該方法可以實現資料庫的動態切換,DynamicDataSource.java
/** * 獲取資料來源(依賴於 spring) 定義一個類繼承AbstractRoutingDataSource實現determineCurrentLookupKey方法,該方法可以實現資料庫的動態切換 */ public class DynamicDataSource extends AbstractRoutingDataSource { public static DynamicDataSource build() { return new DynamicDataSource(); } /** * 獲取與資料來源相關的key * 此key是Map<String,DataSource> resolvedDataSources 中與資料來源繫結的key值 * 在通過determineTargetDataSource獲取目標資料來源時使用 */ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
7.資料來源核心配置類,DataSourceConfiguration.java
/** * 資料來源配置類 */ @Configuration public class DataSourceConfiguration { /** * 主庫 */ @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DataSourceProperties dataSourceProperties) { return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } /** * 從庫 */ @Bean @ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否開啟資料來源開關---若不開啟 預設適用預設資料來源 @ConfigurationProperties("spring.datasource.druid.slave") public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) { return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } /** * 設定資料來源 */ @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); DynamicDataSource dynamicDataSource = DynamicDataSource.build(); targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource); //預設資料來源配置 DefaultTargetDataSource dynamicDataSource.setDefaultTargetDataSource(masterDataSource); //額外資料來源配置 TargetDataSources dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); return dynamicDataSource; } }
8.多資料來源切面配置類,用於獲取註解上的註解,進行動態切換資料來源,DynamicDataSourceAspect.java
@Aspect @Component @Order(-1) // 保證該AOP在@Transactional之前執行 public class DynamicDataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.fuzongle.tankboot.common.annotation.DataSource)" + "|| @within(com.fuzongle.tankboot.common.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { Method targetMethod = this.getTargetMethod(point); DataSource dataSource = targetMethod.getAnnotation(DataSource.class);//獲取要切換的資料來源 if (dataSource != null) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name()); } try { return point.proceed(); } finally { // 銷燬資料來源 在執行方法之後 DynamicDataSourceContextHolder.removeDataSourceType(); } } /** * 獲取目標方法 */ private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException { Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method agentMethod = methodSignature.getMethod(); return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes()); } }
9.編寫業務邏輯,切換從庫查詢資料。
10.編寫測試方法,呼叫查詢業務,檢視是否切換資料來源是否生效。
PS:這種多資料來源的動態切換確實可以解決資料的主從分庫操作,但是卻有一個致命的BUG,那就是事務不但失效而且無法實現
一致性,因為涉及到跨庫,因此我們必須另想辦法來實現事務的ACID原則
以上配置所有原始碼地址:https://gitee.com/fuzongle/java-bucket)
注意:
1.如果有任何不懂的地方可以關注公眾號就可以加我微信,隨時歡迎互相幫助。
2.技術交流群QQ:422167709。
3.如果希望學習更多,希望微信掃碼,長按掃碼,幫忙關注一下,舉手之勞,當您無助的時候真的能幫你。非常感謝您關注公眾號 "程式設計小樂"。