在实际项目开发过程中,因为业务发展和技术实现的需要,我们会在项目中同时连接多个数据库,具体该怎么做呢?
spring:
application:
name: springboot-multisources
datasource:
primary:
pool-name: primary-test_01
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/test_01
username: root
password: root
secondary:
pool-name: secondary-test_02
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/test_02
username: root
password: root
third:
pool-name: secondary-test_03
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/test_03
username: root
password: root
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "thirdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.third")
public DataSource thirdDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource,
@Qualifier("thirdDataSource") DataSource thirdDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(3);
targetDataSources.put(DynamicDataSource.Type.primary.name(), primaryDataSource);
targetDataSources.put(DynamicDataSource.Type.secondary.name(), secondaryDataSource);
targetDataSources.put(DynamicDataSource.Type.third.name(), thirdDataSource);
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setDefaultTargetDataSource(primaryDataSource);
dataSource.setTargetDataSources(targetDataSources);
return dataSource;
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDataSource {
/**
* 要切换数据库的标识
*
* @return
*/
Type value() default Type.primary;
enum Type {
/**
* 主库
*/
primary,
/**
* 第二个库
*/
secondary,
/**
* 第三个库
*/
third
}
}
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
解决异步执行时上下文传递的问题
定义一个拦截器,在请求时检查Controller类和方法上的注解,根据配置的值设置当前请求用什么数据源。在请求响应回去时清除当前线程绑定的数据,做一个资源的释放动作。
public class DataSourceInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
DynamicDataSource.Type type = null;
// 从controller类的方法上找注解
HandlerMethod handlerMethod = ((HandlerMethod) handler).getResolvedFromHandlerMethod();
if (null != handlerMethod) {
DynamicDataSource methodAnnotation = handlerMethod.getMethodAnnotation(DynamicDataSource.class);
if (null != methodAnnotation) {
type = methodAnnotation.value();
}
}
// 如果上面找不到,则尝试到类头上找注解。
if (null == type) {
DynamicDataSource classAnnotation = ((HandlerMethod) handler).getBean().getClass().getAnnotation(DynamicDataSource.class);
type = classAnnotation.value();
}
// 如果有指定数据源就切换,否则使用默认的。
if (null != type) {
DynamicDataSourceContextHolder.setDataSourceKey(type.name());
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DataSourceInterceptor());
}
}
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@DynamicDataSource(DynamicDataSource.Type.secondary)
@GetMapping("users")
List<User> users() {
return userService.listUsers();
}
}
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.8'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
maven {
url 'https://maven.aliyun.com/repository/public/'
}
maven {
url 'https://maven.aliyun.com/repository/spring/'
}
maven {
url 'https://maven.aliyun.com/repository/gradle-plugin'
}
mavenLocal()
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}