SpringBoot基于AbstractRoutingDataSource實現(xiàn)多數(shù)據(jù)源動態(tài)切換
一、場景
在生產(chǎn)業(yè)務中,有一些任務執(zhí)行了耗時較長的查詢操作,在實時性要求不高的時候,我們希望將這些查詢sql分離出來,去從庫查詢,以減少應用對主數(shù)據(jù)庫的壓力。
一種方案是在配置文件中配置多個數(shù)據(jù)源,然后通過配置類來獲取數(shù)據(jù)源以及mapper相關的掃描配置,不同的數(shù)據(jù)源配置不佟的mapper掃描位置,然后需要哪一個數(shù)據(jù)源就注入哪一個mapper接口即可,這種方法比較簡單。特征是通過mapper掃描位置區(qū)分數(shù)據(jù)源。
第二種方案是配置一個默認使用的數(shù)據(jù)源,然后定義多個其他的數(shù)據(jù)源,使用aop形成注解式選擇數(shù)據(jù)源。此種方案實現(xiàn)的核心是對AbstractRoutingDataSource 類的繼承。這是本文的重點。
二、原理
AbstractRoutingDataSource的多數(shù)據(jù)源動態(tài)切換的核心邏輯是:在程序運行時,把數(shù)據(jù)源數(shù)據(jù)源通過 AbstractRoutingDataSource 動態(tài)織入到程序中,靈活的進行數(shù)據(jù)源切換。
基于AbstractRoutingDataSource的多數(shù)據(jù)源動態(tài)切換,可以實現(xiàn)讀寫分離。邏輯如下:
/**
* 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;
}
/**
* 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.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();
通過實現(xiàn)抽象方法determineCurrentLookupKey指定需要切換的數(shù)據(jù)源
三、代碼示例
示例中主要依賴
com.alibaba.druid;tk.mybatis
定義一個類用于關聯(lián)數(shù)據(jù)源。通過 TheadLocal 來保存每個線程選擇哪個數(shù)據(jù)源的標志(key)
@Slf4j
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<String>();
public static void setDataSourceType(String dataSourceType) {
log.info("設置當前數(shù)據(jù)源為{}",dataSourceType);
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get() ;
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId){
log.info("list = {},dataId={}", JSON.toJSON(dataSourceIds),dataSourceId);
return dataSourceIds.contains(dataSourceId);
}
}
繼承
AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
配置主數(shù)據(jù)庫master 與從數(shù)據(jù)庫slave(略)。數(shù)據(jù)源配置可以從簡
@Configuration
@tk.mybatis.spring.annotation.MapperScan(value = {"com.server.dal.dao"})
@ConditionalOnProperty(name = "java.druid.datasource.master.url")
public class JavaDruidDataSourceConfiguration {
private static final Logger logger = LoggerFactory.getLogger(JavaDruidDataSourceConfiguration.class);
@Resource
private JavaDruidDataSourceProperties druidDataSourceProperties;
@Primary
@Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close")
@ConditionalOnMissingBean(name = "masterDataSource")
public DruidDataSource javaReadDruidDataSource() {
DruidDataSource result = new DruidDataSource();
try {
// result.setName(druidDataSourceProperties.getName());
result.setUrl(druidDataSourceProperties.getUrl());
result.setUsername(druidDataSourceProperties.getUsername());
result.setPassword(druidDataSourceProperties.getPassword());
result.setConnectionProperties(
"config.decrypt=false;config.decrypt.key=" + druidDataSourceProperties.getPwdPublicKey());
result.setFilters("config");
result.setMaxActive(druidDataSourceProperties.getMaxActive());
result.setInitialSize(druidDataSourceProperties.getInitialSize());
result.setMaxWait(druidDataSourceProperties.getMaxWait());
result.setMinIdle(druidDataSourceProperties.getMinIdle());
result.setTimeBetweenEvictionRunsMillis(druidDataSourceProperties.getTimeBetweenEvictionRunsMillis());
result.setMinEvictableIdleTimeMillis(druidDataSourceProperties.getMinEvictableIdleTimeMillis());
result.setValidationQuery(druidDataSourceProperties.getValidationQuery());
result.setTestWhileIdle(druidDataSourceProperties.isTestWhileIdle());
result.setTestOnBorrow(druidDataSourceProperties.isTestOnBorrow());
result.setTestOnReturn(druidDataSourceProperties.isTestOnReturn());
result.setPoolPreparedStatements(druidDataSourceProperties.isPoolPreparedStatements());
result.setMaxOpenPreparedStatements(druidDataSourceProperties.getMaxOpenPreparedStatements());
if (druidDataSourceProperties.isEnableMonitor()) {
StatFilter filter = new StatFilter();
filter.setLogSlowSql(druidDataSourceProperties.isLogSlowSql());
filter.setMergeSql(druidDataSourceProperties.isMergeSql());
filter.setSlowSqlMillis(druidDataSourceProperties.getSlowSqlMillis());
List<Filter> list = new ArrayList<>();
list.add(filter);
result.setProxyFilters(list);
}
} catch (Exception e) {
logger.error("數(shù)據(jù)源加載失敗:", e);
} finally {
result.close();
}
return result;
}
}
注意主從數(shù)據(jù)庫的bean name
配置DynamicDataSource
- targetDataSources 存放數(shù)據(jù)源的k-v對
- defaultTargetDataSource 存放默認數(shù)據(jù)源
配置事務管理器和SqlSessionFactoryBean
@Configuration
public class DynamicDataSourceConfig {
? ? private static final String MAPPER_LOCATION = "classpath*:sqlmap/dao/*Mapper.xml";
?
? ? @Bean(name = "dynamicDataSource")
? ? public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DruidDataSource masterDataSource,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?@Qualifier("slaveDataSource") DruidDataSource slaveDataSource) {
? ? ? ? Map<Object, Object> targetDataSource = new HashMap<>();
? ? ? ? DynamicDataSourceContextHolder.dataSourceIds.add("masterDataSource");
? ? ? ? targetDataSource.put("masterDataSource", masterDataSource);
? ? ? ? DynamicDataSourceContextHolder.dataSourceIds.add("slaveDataSource");
? ? ? ? targetDataSource.put("slaveDataSource", slaveDataSource);
? ? ? ? DynamicDataSource dataSource = new DynamicDataSource();
? ? ? ? dataSource.setTargetDataSources(targetDataSource);
? ? ? ? dataSource.setDefaultTargetDataSource(masterDataSource);
? ? ? ? return dataSource;
? ? }
?
? ? @Primary
? ? @Bean(name = "javaTransactionManager")
? ? @ConditionalOnMissingBean(name = "javaTransactionManager")
? ? public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource druidDataSource) {
? ? ? ? return new DataSourceTransactionManager(druidDataSource);
? ? }
?
? ? @Bean(name = "sqlSessionFactoryBean")
? ? public SqlSessionFactoryBean myGetSqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource ?dataSource) {
? ? ? ? SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
? ? ? ? ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
? ? ? ? try {
? ? ? ? ? ? sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION));
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? sqlSessionFactoryBean.setDataSource(dataSource);
? ? ? ? return sqlSessionFactoryBean;
? ? }
}定義一個注解用于指定數(shù)據(jù)源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
切面的業(yè)務邏輯。注意指定order,以確保在開啟事務之前執(zhí)行 。
@Aspect
@Slf4j
@Order(-1)
@Component
public class DataSourceAop {
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) {
String dsId = targetDataSource.value();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
log.error("數(shù)據(jù)源[{}]不存在,使用默認數(shù)據(jù)源 > {}" + targetDataSource.value() + point.getSignature());
} else {
log.info("UseDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
log.info("RevertDataSource : {} > {}"+targetDataSource.value()+point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
以上略去了pom.xml和application.yml
使用示例
@Resource
private ShopBillDOMapper shopBillDOMapper;
//使用默認數(shù)據(jù)源
public ShopBillBO queryTestData(Integer id){
return shopBillDOMapper.getByShopBillId(id);
}
//切換到指定的數(shù)據(jù)源
@TargetDataSource("slaveDataSource")
public ShopBill queryTestData2(Integer id){
return shopBillDOMapper.getByShopBillId(id);
}
如果返回不同的結果就成功了!
到此這篇關于SpringBoot基于AbstractRoutingDataSource實現(xiàn)多數(shù)據(jù)源動態(tài)切換的文章就介紹到這了,更多相關SpringBoot 多數(shù)據(jù)源動態(tài)切換內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- springboot dynamic多數(shù)據(jù)源demo以及常見切換、事務的問題
- Springboot實現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot多數(shù)據(jù)源配置并通過注解實現(xiàn)動態(tài)切換數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源切換實現(xiàn)代碼(Mybaitis)
- SpringBoot實現(xiàn)多數(shù)據(jù)源的切換實踐
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
- springboot中mybatis多數(shù)據(jù)源動態(tài)切換實現(xiàn)
- Springboot如何設置多數(shù)據(jù)源,隨時切換
相關文章
File.createTempFile創(chuàng)建臨時文件的示例詳解
這篇文章主要介紹了File.createTempFile創(chuàng)建臨時文件的示例詳解,在默認臨時文件目錄中創(chuàng)建一個空文件,使用給定前綴和后綴生成其名稱。 如果感興趣來了解一下2020-07-07
SpringBoot使用freemarker導出word文件方法詳解
這篇文章主要介紹了SpringBoot使用freemarker導出word文件方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-11-11
詳解Java使用Jsch與sftp服務器實現(xiàn)ssh免密登錄
這篇文章主要介紹了詳解Java使用Jsch與sftp服務器實現(xiàn)ssh免密登錄,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10
Spring框架JavaMailSender發(fā)送郵件工具類詳解
這篇文章主要為大家詳細介紹了Spring框架JavaMailSender發(fā)送郵件工具類,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04
基于mybatis batch實現(xiàn)批量提交大量數(shù)據(jù)
這篇文章主要介紹了基于mybatis batch實現(xiàn)批量提交大量數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-05-05

