背景:一個(gè)項(xiàng)目中可能存在多數(shù)據(jù)源的情況,雖然微服務(wù)中,一般是單數(shù)據(jù)源,但是例如后臺(tái)管理這些管理接口則不適合使用微服務(wù)來 提供接口,所以業(yè)務(wù)庫也需要共存于后臺(tái)管理項(xiàng)目,而后臺(tái)管理項(xiàng)目中則有自己本身的一個(gè)權(quán)限數(shù)據(jù)庫,則就會(huì)存在多數(shù)據(jù)源的情況。 思路:Spring本身已經(jīng)有實(shí)現(xiàn)數(shù)據(jù)源切換的功能類,可以實(shí)現(xiàn)在項(xiàng)目運(yùn)行時(shí)根據(jù)相應(yīng)key值切換到對應(yīng)的數(shù)據(jù)源DataSource上。 我們只需擴(kuò)展實(shí)現(xiàn)即可。 并結(jié)合數(shù)據(jù)源動(dòng)態(tài)切換為需要切換數(shù)據(jù)源的方法增加注解,從而實(shí)現(xiàn)對帶有注解的攔截切換。 問題:事務(wù)控制,缺省數(shù)據(jù)源生效,而切換為第二數(shù)據(jù)源時(shí),事務(wù)的數(shù)據(jù)源默認(rèn)采用了缺省的。 網(wǎng)上有說更改切面和事務(wù)的執(zhí)行順序,但是試驗(yàn)后并未成功。
以下是為動(dòng)態(tài)數(shù)據(jù)源切換,及缺省事務(wù)第二數(shù)據(jù)源的事務(wù)控制的實(shí)現(xiàn)方案,以springboot作為基礎(chǔ)框架。
使用druid做數(shù)據(jù)源監(jiān)控與管理spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver druid: first: #數(shù)據(jù)源1 url: jdbc:mysql://127.0.0.01:63885/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 username: demo password: demo rongyuan: #數(shù)據(jù)源2 url: jdbc:mysql://127.0.0.01:63885/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 username: demo password: demo initial-size: 10 max-active: 100 min-idle: 10 max-wait: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false stat-view-servlet: enabled: true url-pattern: /druid/* #login-username: admin #login-password: admin filter: stat: log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true構(gòu)建數(shù)據(jù)源及注入到動(dòng)態(tài)數(shù)據(jù)源中
package io.y.common.datasources; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; /** * @title * @author zengzp * @time 2018年7月25日 上午11:22:46 * @Description */ @Configuration // 加上此注解禁用數(shù)據(jù)源自動(dòng)配置 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class DynamicDataSourceConfig { @Bean(name="first") @ConfigurationProperties("spring.datasource.druid.first") public DataSource firstDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean(name="rongyuan") @ConfigurationProperties("spring.datasource.druid.rongyuan") public DataSource secondDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource(@Qualifier("first")DataSource firstDataSource, @Qualifier("rongyuan")DataSource secondDataSource) { Map繼承spring的動(dòng)態(tài)實(shí)現(xiàn),及重寫數(shù)據(jù)源的獲取方法targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceNames.FIRST, firstDataSource); targetDataSources.put(DataSourceNames.SECOND, secondDataSource); return new DynamicDataSource(firstDataSource, targetDataSources); } }
package io.y.common.datasources; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @title * @author zengzp * @time 2018年7月25日 上午 10:20:31 * @Description */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal定義數(shù)據(jù)源切換注解contextHolder = new ThreadLocal<>(); public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(new HashMap<>(targetDataSources)); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
package io.y.common.datasources.annotation; import java.lang.annotation.*; /** * @title 多數(shù)據(jù)源注解 * @author zengzp * @time 2018年7月25日 下午14:50:53 * @Description */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name() default ""; }定義切面,用來攔截帶注解的方法,并在方法執(zhí)行前實(shí)現(xiàn)數(shù)據(jù)源的切換
package io.y.common.datasources.aspect; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import io.y.common.datasources.DataSourceNames; import io.y.common.datasources.DynamicDataSource; import io.y.common.datasources.annotation.TargetDataSource; /** * @title 多數(shù)據(jù)源切面處理類 * @author zengzp * @time 2018年7月25日 下午11:56:43 * @Description */ @Aspect @Component @Order(0) public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(io.y.common.datasources.annotation.TargetDataSource)") public void dataSourcePointCut() { } @Before("dataSourcePointCut()") public void around(JoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); TargetDataSource ds = method.getAnnotation(TargetDataSource.class); if(ds == null){ DynamicDataSource.setDataSource(DataSourceNames.FIRST); logger.debug("set datasource is " + DataSourceNames.FIRST); }else { DynamicDataSource.setDataSource(ds.name()); logger.debug("set datasource is " + ds.name()); } } @AfterReturning("dataSourcePointCut()") public void after(){ DynamicDataSource.clearDataSource(); logger.debug("clean datasource"); } }數(shù)據(jù)源名稱常量類
package io.y.common.datasources; /** * @title 增加多數(shù)據(jù)源,在此配置 * @author zengzp * @time 2018年7月25日 下午4:55:20 * @Description */ public interface DataSourceNames { String FIRST = "first"; String SECOND = "rongyuan"; }
第二數(shù)據(jù)源事務(wù)控制處理以上已經(jīng)完成了動(dòng)態(tài)數(shù)據(jù)源的切換,只需在Service方法上加上@TargetDataScoure注解并且指定需要切換的數(shù)據(jù)源名稱,first數(shù)據(jù)源為缺省數(shù)據(jù)源。
如果使用@Transactional,缺省數(shù)據(jù)源的事務(wù)正常執(zhí)行,如果使@TargetDataScoure切換為第二數(shù)據(jù)源并執(zhí)行事務(wù)時(shí),則數(shù)據(jù)源切換失敗。
問題分析:
大多數(shù)項(xiàng)目只需要一個(gè)事務(wù)管理器。如果存在多數(shù)據(jù)源的情況,事務(wù)管理器是否會(huì)生效,由于spingboot約定大于配置的理念, 默認(rèn)事務(wù)管理器無需我們再聲明定義,而是默認(rèn)加載時(shí)已經(jīng)指定了其數(shù)據(jù)源,其數(shù)據(jù)源則為缺省數(shù)據(jù)源,如果執(zhí)行事務(wù)時(shí)是第二數(shù)據(jù)源,則 還會(huì)以第一數(shù)據(jù)源做處理,這時(shí)則會(huì)異常。
定義事務(wù)管理器 并指定其對應(yīng)管理的數(shù)據(jù)源和聲明name
package io.y.common.datasources; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; /** * @title 多事物管理器配置 * @author zengzp * @time 2018年7月25日 下午4:55:33 * @Description */ @Configuration public class TransactionConfig { public final static String DEFAULT_TX = "defaultTx"; public final static String RONGYUAN_TX = "rongyuanTx"; @Bean(name=TransactionConfig.DEFAULT_TX) public DataSourceTransactionManager transaction(@Qualifier(DataSourceNames.FIRST)DataSource firstDataSource){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(firstDataSource); return dataSourceTransactionManager; } @Bean(name=TransactionConfig.RONGYUAN_TX) public DataSourceTransactionManager rongyuanTransaction(@Qualifier(DataSourceNames.SECOND) DataSource rongyuanDataScoure){ DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(rongyuanDataScoure); return dataSourceTransactionManager; } }
2.事務(wù)管理器使用在@Transactional上指定使用哪個(gè)名稱的事務(wù)管理器
@Override @Transactional(value=TransactionConfig.RONGYUAN_TX, rollbackFor=Exception.class) @TargetDataSource(name = "rongyuan") public void deleteBatch(Integer[] advertIds) { if (advertIds == null || advertIds.length <= 0) { throw new IllegalArgumentException("參數(shù)異常"); } advertDao.deleteBatch(advertIds); }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/76516.html
摘要:多數(shù)據(jù)源,一般用于對接多個(gè)業(yè)務(wù)上獨(dú)立的數(shù)據(jù)庫可能異構(gòu)數(shù)據(jù)庫。這也就導(dǎo)致異構(gòu)數(shù)據(jù)庫的檢查也是類似問題。內(nèi)容略數(shù)據(jù)源多數(shù)據(jù)源,涉及到異構(gòu)數(shù)據(jù)庫,必須明確指定,否則的轉(zhuǎn)換出錯(cuò)取值內(nèi)容可參考初始連接數(shù)最大連接池?cái)?shù)量。 開篇之前,說一句題外話。多數(shù)據(jù)源和動(dòng)態(tài)數(shù)據(jù)源的區(qū)別。 多數(shù)據(jù)源,一般用于對接多個(gè)業(yè)務(wù)上獨(dú)立的數(shù)據(jù)庫(可能異構(gòu)數(shù)據(jù)庫)。 動(dòng)態(tài)數(shù)據(jù)源,一般用于大型應(yīng)用對數(shù)據(jù)切分。 配置參考 如...
摘要:配置想想,我們需要哪些數(shù)據(jù)庫要用到,數(shù)據(jù)庫連接池要用到橋接器要用到,因此要倉庫點(diǎn)我去倉庫中找到搜索這些加進(jìn)去。 本文旨在用最通俗的語言講述最枯燥的基本知識(shí) 最近身邊的程序員掀起了學(xué)習(xí)springboot的熱潮,說什么學(xué)會(huì)了springboot在大街上就可以橫著走、什么有了springboot媽媽再也不擔(dān)心我的編程了、什么BAT都喜歡的框架...聽得作者那個(gè)心癢癢的,于是找了個(gè)時(shí)間,下載...
摘要:申請連接時(shí)執(zhí)行檢測連接是否有效,做了這個(gè)配置會(huì)降低性能。作者在版本中使用,通過監(jiān)控界面發(fā)現(xiàn)有緩存命中率記錄,該應(yīng)該是支持。允許和不允許單條語句返回多個(gè)數(shù)據(jù)集取決于驅(qū)動(dòng)需求使用列標(biāo)簽代替列名稱。需要驅(qū)動(dòng)器支持。將自動(dòng)映射所有復(fù)雜的結(jié)果。 項(xiàng)目github地址:https://github.com/5-Ason/aso... 具體可看 ./db/db-mysql 模塊 本文主要實(shí)現(xiàn)的是對...
閱讀 1293·2021-09-23 11:51
閱讀 1446·2021-09-04 16:45
閱讀 650·2019-08-30 15:54
閱讀 2099·2019-08-30 15:52
閱讀 1626·2019-08-30 11:17
閱讀 3124·2019-08-29 13:59
閱讀 2046·2019-08-28 18:09
閱讀 403·2019-08-26 12:15