SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換
一、場(chǎng)景
在生產(chǎn)業(yè)務(wù)中,有一些任務(wù)執(zhí)行了耗時(shí)較長(zhǎng)的查詢操作,在實(shí)時(shí)性要求不高的時(shí)候,我們希望將這些查詢sql分離出來,去從庫(kù)查詢,以減少應(yīng)用對(duì)主數(shù)據(jù)庫(kù)的壓力。
一種方案是在配置文件中配置多個(gè)數(shù)據(jù)源,然后通過配置類來獲取數(shù)據(jù)源以及mapper相關(guān)的掃描配置,不同的數(shù)據(jù)源配置不佟的mapper掃描位置,然后需要哪一個(gè)數(shù)據(jù)源就注入哪一個(gè)mapper接口即可,這種方法比較簡(jiǎn)單。特征是通過mapper掃描位置區(qū)分?jǐn)?shù)據(jù)源。
第二種方案是配置一個(gè)默認(rèn)使用的數(shù)據(jù)源,然后定義多個(gè)其他的數(shù)據(jù)源,使用aop形成注解式選擇數(shù)據(jù)源。此種方案實(shí)現(xiàn)的核心是對(duì)AbstractRoutingDataSource 類的繼承。這是本文的重點(diǎn)。
二、原理
AbstractRoutingDataSource的多數(shù)據(jù)源動(dòng)態(tài)切換的核心邏輯是:在程序運(yùn)行時(shí),把數(shù)據(jù)源數(shù)據(jù)源通過 AbstractRoutingDataSource 動(dòng)態(tài)織入到程序中,靈活的進(jìn)行數(shù)據(jù)源切換。
基于AbstractRoutingDataSource的多數(shù)據(jù)源動(dòng)態(tài)切換,可以實(shí)現(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();
通過實(shí)現(xiàn)抽象方法determineCurrentLookupKey指定需要切換的數(shù)據(jù)源
三、代碼示例
示例中主要依賴
com.alibaba.druid;tk.mybatis
定義一個(gè)類用于關(guān)聯(lián)數(shù)據(jù)源。通過 TheadLocal 來保存每個(gè)線程選擇哪個(gè)數(shù)據(jù)源的標(biāo)志(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è)置當(dāng)前數(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ù)庫(kù)master 與從數(shù)據(jù)庫(kù)slave(略)。數(shù)據(jù)源配置可以從簡(jiǎn)
@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ù)庫(kù)的bean name
配置DynamicDataSource
- targetDataSources 存放數(shù)據(jù)源的k-v對(duì)
- defaultTargetDataSource 存放默認(rèn)數(shù)據(jù)源
配置事務(wù)管理器和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; ? ? } }
定義一個(gè)注解用于指定數(shù)據(jù)源
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); }
切面的業(yè)務(wù)邏輯。注意指定order,以確保在開啟事務(wù)之前執(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ù)源[{}]不存在,使用默認(rèn)數(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; //使用默認(rèn)數(shù)據(jù)源 public ShopBillBO queryTestData(Integer id){ return shopBillDOMapper.getByShopBillId(id); } //切換到指定的數(shù)據(jù)源 @TargetDataSource("slaveDataSource") public ShopBill queryTestData2(Integer id){ return shopBillDOMapper.getByShopBillId(id); }
如果返回不同的結(jié)果就成功了!
到此這篇關(guān)于SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動(dòng)態(tài)切換的文章就介紹到這了,更多相關(guān)SpringBoot 多數(shù)據(jù)源動(dòng)態(tài)切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot dynamic多數(shù)據(jù)源demo以及常見切換、事務(wù)的問題
- Springboot實(shí)現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot多數(shù)據(jù)源配置并通過注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源
- SpringBoot多數(shù)據(jù)源切換實(shí)現(xiàn)代碼(Mybaitis)
- SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換實(shí)踐
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
- springboot中mybatis多數(shù)據(jù)源動(dòng)態(tài)切換實(shí)現(xiàn)
- Springboot如何設(shè)置多數(shù)據(jù)源,隨時(shí)切換
相關(guān)文章
File.createTempFile創(chuàng)建臨時(shí)文件的示例詳解
這篇文章主要介紹了File.createTempFile創(chuàng)建臨時(shí)文件的示例詳解,在默認(rèn)臨時(shí)文件目錄中創(chuàng)建一個(gè)空文件,使用給定前綴和后綴生成其名稱。 如果感興趣來了解一下2020-07-07一篇文章帶你了解mybatis的動(dòng)態(tài)SQL
這篇文章主要為大家介紹了mybatis的動(dòng)態(tài)SQL?,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01SpringBoot使用freemarker導(dǎo)出word文件方法詳解
這篇文章主要介紹了SpringBoot使用freemarker導(dǎo)出word文件方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-11-11詳解Java使用Jsch與sftp服務(wù)器實(shí)現(xiàn)ssh免密登錄
這篇文章主要介紹了詳解Java使用Jsch與sftp服務(wù)器實(shí)現(xiàn)ssh免密登錄,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10Spring框架JavaMailSender發(fā)送郵件工具類詳解
這篇文章主要為大家詳細(xì)介紹了Spring框架JavaMailSender發(fā)送郵件工具類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04基于mybatis batch實(shí)現(xiàn)批量提交大量數(shù)據(jù)
這篇文章主要介紹了基于mybatis batch實(shí)現(xiàn)批量提交大量數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05