pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xcg</groupId> <artifactId>xcg-back</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>xcg-back</name> <properties> <elasticsearch.version>7.4.1</elasticsearch.version> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.7.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.7.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.51</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.23</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.4.0</version> </dependency> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency> </dependencies> </project>
application.yml
spring: datasource: db01: db-type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://xxx:3306/testdb1?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false username: root password: root validation-query: SELECT 1 FROM DUAL #測試連線是否可用的SQL語句 initial-size: 10 #資料庫連線池初始化連線數量 min-idle: 10 #資料庫連線池最小連線數量 max-active: 30 #資料庫連線池最大連線數量 max-wait: 30000 # 配置獲取連線等待超時的時間 30000毫秒(30秒) db02: db-type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://xxx:3306/testdb2?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false username: root password: root validation-query: SELECT 1 FROM DUAL #測試連線是否可用的SQL語句 initial-size: 10 #資料庫連線池初始化連線數量 min-idle: 10 #資料庫連線池最小連線數量 max-active: 30 #資料庫連線池最大連線數量 max-wait: 30000 # 配置獲取連線等待超時的時間 30000毫秒(30秒)
配置檔案繫結類,把yml中的配置轉為類map繫結資料來源
import com.alibaba.druid.pool.DruidDataSource; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.Map; @Data @ConfigurationProperties(prefix = "spring") public class DynamicDatasourceProperties { private Map<String, DruidDataSource> datasource; }
資料來源切換類,使用ThreadLocal,每個執行緒使用自己的資料來源副本。
public class DynamicDataSourceContextHolder { /** * 動態資料來源名稱上下文 */ private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>(); /** * 設定/切換資料來源 */ public static void setContextKey(String key) { DATASOURCE_CONTEXT_KEY_HOLDER.set(key); } /** * 獲取資料來源名稱 */ public static String getContextKey() { String key = DATASOURCE_CONTEXT_KEY_HOLDER.get(); return key == null ? DataSourceConstants.DS_KEY_MASTER : key; } /** * 刪除當前資料來源名稱 */ public static void removeContextKey() { DATASOURCE_CONTEXT_KEY_HOLDER.remove(); } }
繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 動態資料來源 * */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getContextKey(); } }
配置資料來源
import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.HashMap; @Slf4j @Configuration public class DataSourceConfig { @Configuration @EnableConfigurationProperties(DynamicDatasourceProperties.class) @MapperScan(basePackages = "com.xcg.mapper", sqlSessionTemplateRef = "otherSqlSessionTemplate") public static class DynamicDatasourceConfiguration { @Resource private DynamicDatasourceProperties dynamicDatasourceProperties; @Bean(name = "otherDataSource") public DynamicDataSource otherDataSource(){ HashMap<Object, Object> dataSourceMap = new HashMap<>(dynamicDatasourceProperties.getDatasource()); DynamicDataSource dynamicDatasource = new DynamicDataSource(); dynamicDatasource.setTargetDataSources(dataSourceMap); dynamicDatasource.setDefaultTargetDataSource(dataSourceMap.get("master")); return dynamicDatasource; } @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/*.xml")); bean.setTypeAliasesPackage("com.xcg.model"); return bean.getObject(); } @Bean(name = "otherSqlSessionTemplate") public SqlSessionTemplate otherSqlSessionTemplate(@Qualifier("otherSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } } }
aop
import com.xcg.annotation.DS; import com.xcg.config.DynamicDataSourceContextHolder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @Aspect @Component @Slf4j public class DynamicDataSourceAspect { //定義切入點 //@Pointcut("execution(* com.xcg.mapper.*.*(..))") com.railway.mapper 包下所有方法,不管什麼返回值和引數。 //@Pointcut("@annotation(com.xcg.annotation.DS)") 所有使用了 DS 註解的地方 @Pointcut("@annotation(com.xcg.annotation.DS)") public void pointcut() { } //可以多個 && @annotation(permissions) && @annotation(ds) // @Around("pointcut()") @Around("pointcut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { //資料來源名稱 String dsKey = getDSAnnotation(joinPoint); DynamicDataSourceContextHolder.setContextKey(dsKey); try { return joinPoint.proceed(); } finally { DynamicDataSourceContextHolder.removeContextKey(); } } /** * 獲取第一個引數作為資料來源 */ private String getDSAnnotation(ProceedingJoinPoint joinPoint) { //DS設定到類上面沒用,只能設定到mapper的方法上面,第一個引數指定資料來源。 String dsKey = null; Object[] args = joinPoint.getArgs(); if (null != args[0]) { String key = args[0].toString(); dsKey = "db" + key; } if (StringUtils.isEmpty(dsKey)) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); dsKey = methodSignature.getMethod().getAnnotation(DS.class).value(); } return dsKey; } }
註解
import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DS { String value() default "01"; }