参考:https://blog.csdn.net/qq_37759895/article/details/135742006
pom.xml 我这里给出了我练习用的demo上的所有导入包,反正不会错。SpringBoot版本3.3.1
application.yml
配置文件绑定类,把yml中的配置转为类map绑定数据源
数据源切换类,使用ThreadLocal,每个线程使用自己的数据源副本。
pom.xml 我这里给出了我练习用的demo上的所有导入包,反正不会错。SpringBoot版本3.3.1
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.xcg</groupId> <artifactId>webapp</artifactId> <version>1.0.0</version> <name>webapp</name> <description>WebApp project for Spring Boot3</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </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>3.3.1</version> </dependency> <!--配置多数据源时,不需要此启动器,不需要单独配置此启动器的数据源。--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-3-starter --> <!-- <dependency>--> <!-- <groupId>com.alibaba</groupId>--> <!-- <artifactId>druid-spring-boot-3-starter</artifactId>--> <!-- <version>1.2.23</version>--> <!-- </dependency>--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.23</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </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> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <!-- <dependency>--> <!-- <groupId>org.apache.logging.log4j</groupId>--> <!-- <artifactId>log4j-core</artifactId>--> <!-- <version>2.23.1</version>--> <!-- </dependency>--> <!-- <!– https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api –>--> <!-- <dependency>--> <!-- <groupId>org.apache.logging.log4j</groupId>--> <!-- <artifactId>log4j-api</artifactId>--> <!-- <version>2.23.1</version>--> <!-- </dependency>--> <!-- 使用SpringBoot自带的log组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </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>3.0.3</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>3.3.1</version> </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.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.34</version> <scope>provided</scope> </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/com.microsoft.sqlserver/mssql-jdbc --> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>mssql-jdbc</artifactId> <version>12.6.2.jre8</version> </dependency> <!--oracle驱动--> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.1.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
server: address: 192.168.9.2 port: 8100 spring: profiles: active: dev #表示开发环境,nacos config application: name: webapp # 数据源类型 datasource: master: db-type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/xcgdb?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秒) db01: db-type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/xcgdb?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.microsoft.sqlserver.jdbc.SQLServerDriver # 注意,连接字符串中加上encrypt=false;trustServerCertificate=true;不然他会使用SSL去连数据库。 url: jdbc:sqlserver://localhost:1433;DatabaseName=testdb;encrypt=false;trustServerCertificate=true;loginTimeout=30; username: sa password: 123456 validation-query: SELECT 1 #测试连接是否可用的SQL语句 initial-size: 10 #数据库连接池初始化连接数量 min-idle: 10 #数据库连接池最小连接数量 max-active: 30 #数据库连接池最大连接数量 max-wait: 30000 # 配置获取连接等待超时的时间 30000毫秒(30秒) testOnBorrow: true # 申请连接时执行validationQuery检测连接是否有效 testWhileIdle: true # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 timeBetweenEvictionRunsMillis: 60000 # 默认60秒。Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 # 日志相关配置 logging: level: root: ERROR file: name: sys.log logback: rollingpolicy: # gz file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i.zip max-file-size: 1MB pattern: dateformat: yyyy-MM-dd HH:mm:ss mybatis: mapper-locations: classpath:mapper/*.xml # 如果指定了mybatis的扫描包,那么在mapper/*.xml中就不好再指定别的目录了,所以这里先注释。 # type-aliases-package: com.xcg.webapp.model.entity
package com.xcg.webapp.config; import com.alibaba.druid.pool.DruidDataSource; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Map; @Data @ConfigurationProperties(prefix = "spring") @Component public class DynamicDatasourceProperties { //绑定配置文件中的数据源 private Map<String, DruidDataSource> datasource; }
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(); } }
配置数据源
@Configuration public class DynamicDataSourceConfig { @Configuration @EnableConfigurationProperties(DynamicDatasourceProperties.class) @MapperScan(basePackages = "com.xcg.webapp.mapper", sqlSessionTemplateRef = "dynamicSqlSessionTemplate") public static class DynamicDatasourceConfiguration { @Resource private DynamicDatasourceProperties dynamicDatasourceProperties; @Bean(name = "dynamicDataSource") public DynamicDataSource dynamicDataSource(){ HashMap<Object, Object> dataSourceMap = new HashMap<>(dynamicDatasourceProperties.getDatasource()); DynamicDataSource dynamicDatasource = new DynamicDataSource(); dynamicDatasource.setTargetDataSources(dataSourceMap); dynamicDatasource.setDefaultTargetDataSource(dataSourceMap.get("master")); return dynamicDatasource; } @Bean(name = "dynamicTransactionManager") public DataSourceTransactionManager dynamicTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "dynamicSqlSessionFactory") public SqlSessionFactory dynamicSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); bean.setTypeAliasesPackage("com.xcg.webapp.model"); return bean.getObject(); } @Bean(name = "dynamicSqlSessionTemplate") public SqlSessionTemplate dynamicSqlSessionTemplate(@Qualifier("dynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } } }
做一个切面AOP,动态选定数据源
package com.xcg.webapp.aspect; import com.xcg.webapp.annotation.DS; import com.xcg.webapp.config.DynamicDataSourceContextHolder; import com.xcg.webapp.config.DynamicDatasourceProperties; 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Aspect @Component public class DynamicDataSourceAspect { @Autowired private DynamicDatasourceProperties dynamicDatasourceProperties; //定义切入点 //@Pointcut("execution(* com.xcg.mapper.*.*(..))") com.railway.mapper 包下所有方法,不管什么返回值和参数。 //@Pointcut("@annotation(com.xcg.annotation.DS)") 所有使用了 DS 注解的地方 @Pointcut("@annotation(com.xcg.webapp.annotation.DS)") public void pointcut() { } //可以多个 @Around("pointcut() && @annotation(permissions) && @annotation(ds)") //@Around("pointcut()") @Around("pointcut() && @annotation(ds)") public Object around(ProceedingJoinPoint joinPoint, DS ds) throws Throwable { //数据源名称 String dsKey = getDSAnnotation(joinPoint); DynamicDataSourceContextHolder.setContextKey(dsKey); try { return joinPoint.proceed(); } finally { DynamicDataSourceContextHolder.removeContextKey(); } } /** * 获取第一个参数作为数据源名称,如果名称在数据源map中不存在, * 则查找方法上面的注解。 */ private String getDSAnnotation(ProceedingJoinPoint joinPoint) { //DS设置到类上面没用,只能设置到mapper的方法上面,第一个参数指定数据源。 String dsKey = null; Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0 && null != args[0]) { String key = args[0].toString(); boolean has = dynamicDatasourceProperties.getDatasource().containsKey(key); if (has) { dsKey = key; } } if (StringUtils.isBlank(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 ""; }
常量
package com.xcg.webapp.config; public class DynamicDataSourceConstants { //主库 public static final String DS_KEY_MASTER = "master"; }
使用,在mapper的方法上面增加注解,或者方法的第一个参数传数据源名称
@Mapper @Repository public interface DbSqlStuMapper { @DS("db02") List<DbSqlStu> getAll(); }