前言
在之前的事件管理系統(tǒng)博客中有提到動(dòng)態(tài)的多數(shù)據(jù)源配置
工作中難免需要做幾個(gè)工具方便自己偷懶,加上之前的擋板,數(shù)據(jù)源肯定沒(méi)法單一配置,所以需要多數(shù)據(jù)源配置。這里介紹兩種配置:動(dòng)態(tài)數(shù)據(jù)源和固定數(shù)據(jù)源模式。這兩種我在目前的工作的工具開(kāi)發(fā)中都有用到。
一、固定數(shù)據(jù)源配置
Mybatis是提供這種固定的多數(shù)據(jù)源配置的,需要分別配置包掃描(一般是不同的數(shù)據(jù)源掃描不同的包),事務(wù)處理器等。
-
yml
配置,主要是不要用Spring Boot
自帶的數(shù)據(jù)庫(kù)配置,spring.datasource
,或者其他數(shù)據(jù)源配置,改用自己的,這樣其實(shí)Spring boot
的數(shù)據(jù)庫(kù)自動(dòng)配置DataSourceAutoConfiguration
其實(shí)是失效了的。
## 這個(gè)是第一個(gè)固定配置
spring:
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.ibm.db2.jcc.DB2Driver
url: jdbc:db2://*****
username: ****
password: ****
initial-size: 1
min-idle: 1
max-active: 1
max-wait: 5000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
connection-error-retry-attempts: 1
break-after-acquire-failure: true
## 這是第二個(gè)動(dòng)態(tài)數(shù)據(jù)源配置
app:
datasource:
mapDatasource:
TESTDATASOURCE1:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://****
username: ****
password: ****
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 5000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
connection-error-retry-attempts: 1
break-after-acquire-failure: true
TESTDATASOURCE2:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://****
username: ****
password: ****
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 5000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
connection-error-retry-attempts: 1
break-after-acquire-failure: true
TESTDATASOURCE3:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.ibm.db2.jcc.DB2Driver
url: jdbc:db2://****
username: ****
password: ****
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 5000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
connection-error-retry-attempts: 1
break-after-acquire-failure: true
TESTDATASOURCE4:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.ibm.db2.jcc.DB2Driver
url: jdbc:db2://****
username: ****
password: ****
initial-size: 5
min-idle: 5
max-active: 10
max-wait: 5000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
max-evictable-idle-time-millis: 900000
connection-error-retry-attempts: 1
break-after-acquire-failure: true
-
Spring Boot
配置類(lèi)
@Data
@ConfigurationProperties(prefix = "app.datasource")
public class SystemDynamicDatasourceProperties {
private Map<String, DruidDataSource> mapDatasource;
}
-
mybatis
固定數(shù)據(jù)源配置
@Configuration
public class DataSourceConfiguration {
@Configuration
@MapperScan(basePackages = "com.test.mapper.datasource1", sqlSessionTemplateRef = "source1SqlSessionTemplate")
public static class source1DatasourceConfiguration {
@Bean(name = "source1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DruidDataSource source1DataSource(){
return new DruidDataSource();
}
@Bean(name = "source1TransactionManager")
public DataSourceTransactionManager source1TransactionManager(@Qualifier("source1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "source1SqlSessionFactory")
public SqlSessionFactory source1SqlSessionFactory(@Qualifier("source1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/source1/*.xml"));
bean.setTypeAliasesPackage("com.source1.entity");
return bean.getObject();
}
@Bean(name = "source1SqlSessionTemplate")
public SqlSessionTemplate source1SqlSessionTemplate(@Qualifier("source1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
@Configuration
@EnableConfigurationProperties(SystemDynamicDatasourceProperties.class)
@MapperScan(basePackages = "com.source1.source1web.mapper.other", sqlSessionTemplateRef = "otherSqlSessionTemplate")
public static class DynamicDatasourceConfiguration {
@Resource
private SystemDynamicDatasourceProperties systemDynamicDatasourceProperties;
@Bean(name = "otherDataSource")
public SystemDynamicDatasource otherDataSource(){
HashMap<Object, Object> map = new HashMap<>(systemDynamicDatasourceProperties.getMapDatasource());
SystemDynamicDatasource systemDynamicDatasource = new SystemDynamicDatasource(map);
return systemDynamicDatasource;
}
@Bean(name = "otherTransactionManager")
public DataSourceTransactionManager otherTransactionManager(@Qualifier("otherDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "otherSqlSessionFactory")
public SqlSessionFactory otherSqlSessionFactory(@Qualifier("otherDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/other/*.xml"));
bean.setTypeAliasesPackage("com.source1.source1web.entity");
return bean.getObject();
}
@Bean(name = "otherSqlSessionTemplate")
public SqlSessionTemplate otherSqlSessionTemplate(@Qualifier("otherSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
- 說(shuō)明
這兩種其實(shí)就已經(jīng)是兩種數(shù)據(jù)源的配置了,當(dāng)使用com.test.mapper.datasource1
包下的Mapper
的時(shí)候,使用的是就Datasource1的數(shù)據(jù)源,包括事務(wù)管理器,當(dāng)使用com.source1.source1web.mapper.other
包下的Mapper
的時(shí)候,就是第二種數(shù)據(jù)源。
但是,這種只適合于單數(shù)據(jù)庫(kù)操作的事務(wù),多數(shù)據(jù)庫(kù)的事務(wù)屬于分布式事務(wù),不適于此,當(dāng)一個(gè)數(shù)據(jù)庫(kù)事務(wù)提交成功之后,另一個(gè)事務(wù)失敗的話(huà),無(wú)法回滾第一個(gè)。因?yàn)榇隧?xiàng)目只適用于查詢(xún)和單數(shù)據(jù)庫(kù)的插入,失敗不做回滾。
二、動(dòng)態(tài)數(shù)據(jù)源
其實(shí)就是在上面的基礎(chǔ)上,上面已經(jīng)配置好了數(shù)據(jù)源,和動(dòng)態(tài)的配置,但是漏掉了一些配置的細(xì)節(jié),就是動(dòng)態(tài)數(shù)據(jù)源,其實(shí)
Mybatis
提供了動(dòng)態(tài)數(shù)據(jù)源的抽象類(lèi)AbstractRoutingDataSource
,我們只需要繼承這個(gè)類(lèi)并重寫(xiě)determineCurrentLookupKey
方法,找到相關(guān)的數(shù)據(jù)源即可。在這個(gè)配置里無(wú)論添加多少數(shù)據(jù)源都可以,動(dòng)態(tài)添加也是可以的。
- 動(dòng)態(tài)數(shù)據(jù)源配置
public class SystemDynamicDatasource extends AbstractRoutingDataSource {
private Map<Object,Object> dataSourceMap;
public static final ThreadLocal<String> DATA_SOURCE = new ThreadLocal<>();
public SystemDynamicDatasource(Map<Object, Object> dataSourceMap){
this.dataSourceMap = dataSourceMap;
super.setTargetDataSources(dataSourceMap);
super.afterPropertiesSet();
}
public void setDataSource(Integer key, DataSource dataSource){
DruidDataSource oldDataSource = (DruidDataSource) dataSourceMap.put(key, dataSource);
if (oldDataSource != null) {
oldDataSource.close();
}
afterPropertiesSet();
}
public void removeDataSource(String key){
DruidDataSource oldDataSource = (DruidDataSource) dataSourceMap.remove(key);
if (oldDataSource != null) {
oldDataSource.close();
}
afterPropertiesSet();
}
public boolean isExist(String key){
return dataSourceMap.get(key) != null;
}
@Override
protected Object determineCurrentLookupKey() {
return DATA_SOURCE.get();
}
public void setDataSource(String dataSource){
DATA_SOURCE.set(dataSource);
}
public static void removeDataSource(){
DATA_SOURCE.remove();
}
}
說(shuō)明:
- 線(xiàn)上使用進(jìn)入多線(xiàn)程環(huán)境,其實(shí)主要區(qū)別就是需要確定當(dāng)前線(xiàn)程使用的是哪個(gè)數(shù)據(jù)源。
Map
里面存儲(chǔ)的就是多數(shù)據(jù)源,其中key
是每個(gè)數(shù)據(jù)源的key
,當(dāng)某個(gè)線(xiàn)程需要確定使用哪個(gè)數(shù)據(jù)源的時(shí)候,就是靠這個(gè)key
來(lái)進(jìn)行區(qū)分的。ThreadLocal
就是確定某個(gè)線(xiàn)程使用的是哪個(gè)key,這樣保證了線(xiàn)程安全,不會(huì)相互影響,只要使用的時(shí)候注意remove即可。determineCurrentLookupKey
調(diào)用來(lái)決定哪個(gè)數(shù)據(jù)源。
-
AOP配置
這里最好使用AOP進(jìn)行統(tǒng)一配置,不要在代碼里寫(xiě),在代碼里寫(xiě)既添加了大量重復(fù)代碼,而且與業(yè)務(wù)相關(guān),代碼可讀性差,最好做成AOP統(tǒng)一配置。
代碼如下:
- 注解
@Target({ElementType.TYPE, ElementType.METHOD}) public @interface OtherDatasource { String value() default ""; }
可以通過(guò)這個(gè)注解來(lái)充當(dāng)切點(diǎn),但是本次使用僅作為額外數(shù)據(jù),獲取指定的數(shù)據(jù)源使用。
- 切面
@Aspect @Component @Slf4j public class OtherDataSourceAspect { @Autowired private SystemDynamicDatasource systemDynamicDatasource; // @Pointcut("@annotation(com.ibank.im.app.aop.cache.annotation.SystemCacheable)") @Pointcut("execution(public * com.test.mapper.other.*.*(..))") public void pointcut(){} @Around("pointcut()") public Object systemCacheableAround(ProceedingJoinPoint joinPoint) throws Throwable { Class<?> targetCls=joinPoint.getTarget().getClass(); OtherDatasource annotation = targetCls.getAnnotation(OtherDatasource.class); String datasource = null; if (Objects.isNull(annotation) || !StringUtils.hasText(datasource = annotation.value())) { MethodSignature methodSignature=(MethodSignature)joinPoint.getSignature(); Method targetMethod= targetCls.getDeclaredMethod( methodSignature.getName(), methodSignature.getParameterTypes()); OtherDatasource methodAnnotation = targetMethod.getAnnotation(OtherDatasource.class); if (Objects.isNull(methodAnnotation) || !StringUtils.hasText(datasource = methodAnnotation.value())) { Object[] args = joinPoint.getArgs(); if (Arrays.isNullOrEmpty(args)) { throw new IllegalArgumentException("must have 1 param"); } if (!(args[0] instanceof String)) { throw new IllegalArgumentException("the first param must be databaseEnv"); } datasource = (String) args[0]; } } if (!systemDynamicDatasource.isExist(datasource)) { throw new IllegalArgumentException("databaseEnv does not exist"); } try{ systemDynamicDatasource.setDataSource(datasource); return joinPoint.proceed(); }finally { systemDynamicDatasource.removeDataSource(datasource); } } }
- 注解
說(shuō)明:
- 本次數(shù)據(jù)源直接在
Mapper
層使用,不在Service
層使用,因?yàn)橐粋€(gè)Service
可能要使用多個(gè)不同的數(shù)據(jù)源操作,比較麻煩,直接作用在Mapper
層比較合適。- 邏輯上,先判斷這個(gè)類(lèi)上有沒(méi)有注解,有的話(huà)使用這個(gè)注解,如果沒(méi)有在使用方法上的注解,方法上如果沒(méi)有注解,就是用第一個(gè)
String
參數(shù),在沒(méi)有就會(huì)報(bào)錯(cuò),在判斷是否存在這個(gè)數(shù)據(jù)源。不存在直接報(bào)錯(cuò)。- 使用的時(shí)候,一定要用try包裹,使用完成
必須remove
掉當(dāng)前的值,無(wú)論是否發(fā)生異常。不移除的話(huà)容易發(fā)生內(nèi)存溢出
等問(wèn)題。- 切面執(zhí)行方法就是
ProceedingJoinPoint
類(lèi)的proceed
方法,但是實(shí)際上這個(gè)方法有兩個(gè)重載的函數(shù),一個(gè)帶參數(shù)一個(gè)不帶參數(shù),這里簡(jiǎn)要介紹一下:
- 不帶參數(shù)的:表示調(diào)用時(shí)傳遞什么參數(shù),就是什么參數(shù),Advice不干預(yù),原樣傳遞。因?yàn)楸敬芜^(guò)程不修改什么參數(shù)。所以使用的是這個(gè)
- 帶參數(shù)的:自然就是相反的,將替換掉調(diào)用時(shí)傳遞的參數(shù),這時(shí)候方法里調(diào)用的就是切面里的參數(shù)。
因?yàn)槟壳皹I(yè)務(wù)需求問(wèn)題,都是使用的參數(shù)進(jìn)行傳遞,所以只能定義在方法參數(shù)上。像這個(gè)樣子:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-817237.html
@Mapper
public interface TestMapper {
int insertTest(String env, Entity entity);
}
第一個(gè)參數(shù)就決定是哪個(gè)數(shù)據(jù)源,但是實(shí)際上業(yè)務(wù)并不采用。因?yàn)闊o(wú)法固定使用某個(gè)數(shù)據(jù)源的問(wèn)題,只能以參數(shù)的方式傳遞。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-817237.html
搞定收工!
到了這里,關(guān)于Spring Boot整合Mybatis配置多數(shù)據(jù)源的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!