方案
- AOP+注解:适用于方法级的数据源动态切换
- 分包法(生成多个SqlSessionFactory,分别指定不同的mapper包)
这里我先介绍常用的方式:AOP+注解
代码
1.配置相关
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
yml文件
mybatis:
type-aliases-package: com.jenkins.domain
mapper-locations: classpath*:/mapper/*.xml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
dbs:
db1:
url: jdbc:mysql://ip:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
db2:
url: jdbc:mysql://ip:3306/db2?useSSL=false&serverTimezone=UTC
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
yml对应配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class DbConfig {
private Map<String, DruidDataSource> dbs;
}
数据源配置类
@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DynamicDataSourceConfig {
private final DbConfig dbConfig;
@Bean("dynamicDataSource")
public DynamicDataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>();
dbConfig.getDbs().forEach(dataSourceMap::put);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(dbConfig.getDbs().get("db1"));
return dynamicDataSource;
}
}
数据源切换类
public class DynamicDataSourceContextHolder {
public static final String DS1_KEY = "db1";
public static final String DS2_KEY = "db2";
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY = new ThreadLocal<>();
public static void setContextKey(String key) {
DATASOURCE_CONTEXT_KEY.set(key);
}
public static String getContextKey() {
String key = DATASOURCE_CONTEXT_KEY.get();
return key == null ? DS1_KEY : key;
}
public static void removeContextKey() {
DATASOURCE_CONTEXT_KEY.remove();
}
}
动态数据源类(重点)
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
2.aop
注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value();
}
切面
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.hui.annotations.DataSource)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try {
return joinPoint.proceed();
} finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
private DataSource getDSAnnotation(ProceedingJoinPoint joinPoint) {
Class<?> targetClass = joinPoint.getTarget().getClass();
DataSource dsAnnotation = targetClass.getAnnotation(DataSource.class);
if (Optional.ofNullable(dsAnnotation).isPresent()) {
return dsAnnotation;
} else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DataSource.class);
}
}
}