前言
操作Java项目的时候,避免不了多数据源

对此怎么在一个项目中灵活切换是个问题

对于Java的相关知识推荐阅读:java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)

采用jpa的依赖包:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
1
2
3
4
配置对应的数据源:application.properties

# 主数据源配置
spring.datasource.primary.url=jdbc:mysql://localhost:3306/primarydb
spring.datasource.primary.username=username
spring.datasource.primary.password=password
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

# 辅助数据源配置
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/secondarydb
spring.datasource.secondary.username=username
spring.datasource.secondary.password=password
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
创建数据源配置:

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

@Primary
@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();
}

@Primary
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("your.primary.entity.package")
.persistenceUnit("primary").build();
}

@Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder builder, @Qualifier("secondaryDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("your.secondary.entity.package")
.persistenceUnit("secondary").build();
}

@Primary
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}

@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
切换数据源,此处使用@Qualifier的注解

@Service
public class YourService {

@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;

@Autowired
@Qualifier("secondaryDataSource")
private DataSource secondaryDataSource;

@Autowired
private YourRepository yourRepository;

@Transactional(transactionManager = "transactionManager")
public void methodUsingPrimaryDataSource() {
// 在这里使用主数据源
}

@Transactional(transactionManager = "secondaryTransactionManager")
public void methodUsingSecondaryDataSource() {
// 在这里使用辅助数据源
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. 基本知识
DynamicDataSourceContextHolder 是一个用于动态数据源切换的工具类

利用 ThreadLocal 存储当前线程的数据源信息,通过栈的方式实现数据源的嵌套切换

其基本知识,主要通过分析源码进行讲解:

 

第一:属性:

LOOKUP_KEY_HOLDER:使用 ThreadLocal 保存一个 Deque(双端队列,通常用作栈)对象,用于存储当前线程的数据源名称,这里使用 Deque 是为了支持嵌套切换数据源,即在一个方法调用链中可以多次切换数据源,并且在方法调用结束后能恢复到上一个数据源

private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
@Override
protected Deque<String> initialValue() {
return new ArrayDeque<>();
}
};
1
2
3
4
5
6
第二:构造方法:(私有构造方法,防止外部实例化该类,因为它是一个工具类,所有方法都是静态方法)

private DynamicDataSourceContextHolder() {
}
1
2
第三:方法

获取当前线程的数据源
peek() 方法返回当前线程栈顶的数据源名称,不移除数据
public static String peek() {
return LOOKUP_KEY_HOLDER.get().peek();
}
1
2
3
设置当前线程的数据源
push(String ds) 方法将数据源名称 ds 压入当前线程的栈中
如果 ds 为空,则压入空字符串
public static String push(String ds) {
String dataSourceStr = DsStrUtils.isEmpty(ds) ? "" : ds;
LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
return dataSourceStr;
}
1
2
3
4
5
清空当前线程的数据源
poll() 方法移除当前线程栈顶的数据源名称
如果栈为空,则移除 ThreadLocal 对象,避免内存泄漏
public static void poll() {
Deque<String> deque = LOOKUP_KEY_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
}
}
1
2
3
4
5
6
7
强制清空当前线程的所有数据源信息
clear() 方法清除当前线程的所有数据源信息,强制移除 ThreadLocal 对象
public static void clear() {
LOOKUP_KEY_HOLDER.remove();
}
1
2
3
2. Demo
引入相应的依赖

<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!-- HikariCP for DataSource pooling -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>

<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在 application.yml 或 application.properties 文件中配置多个数据源

例如,配置两个数据源 dataSource1 和 dataSource2:

spring:
datasource:
dynamic:
primary: dataSource1
datasource:
dataSource1:
url: jdbc:mysql://localhost:3306/db1
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
dataSource2:
url: jdbc:mysql://localhost:3306/db2
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
dataSource3:
url: jdbc:mysql://localhost:3306/db3
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
执行的响应的测试文件:

public class DynamicDataSourceDemo {
public static void main(String[] args) {
// 设置第一个数据源
DynamicDataSourceContextHolder.push("dataSource1");
System.out.println("当前数据源: " + DynamicDataSourceContextHolder.peek());

// 调用 serviceA
serviceA();

// 清空所有数据源信息
DynamicDataSourceContextHolder.clear();
}

public static void serviceA() {
// 在 serviceA 中切换到 dataSource2
DynamicDataSourceContextHolder.push("dataSource2");
System.out.println("serviceA 中的数据源: " + DynamicDataSourceContextHolder.peek());

// 调用 serviceB
serviceB();

// 恢复到上一个数据源
DynamicDataSourceContextHolder.poll();
System.out.println("serviceA 结束后数据源: " + DynamicDataSourceContextHolder.peek());
}

public static void serviceB() {
// 在 serviceB 中切换到 dataSource3
DynamicDataSourceContextHolder.push("dataSource3");
System.out.println("serviceB 中的数据源: " + DynamicDataSourceContextHolder.peek());

// 调用完成后恢复到上一个数据源
DynamicDataSourceContextHolder.poll();
System.out.println("serviceB 结束后数据源: " + DynamicDataSourceContextHolder.peek());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
根据Demo输出结果如下:

当前数据源: dataSource1
serviceA 中的数据源: dataSource2
serviceB 中的数据源: dataSource3
serviceB 结束后数据源: dataSource2
serviceA 结束后数据源: dataSource1
1
2
3
4
5
3. 实战
实战与Demo也差不多

Oracle的数据源配置:

 

执行对应的测试类:

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = manongyanjiuseng.class)
public class projectTest {

@Test
public void testUsingTosDataSource() {
// 设置第一个数据源
DynamicDataSourceContextHolder.push("tos200");
System.out.println("当前数据源: " + DynamicDataSourceContextHolder.peek());

// 调用 serviceA
serviceA();

// 清空所有的数据源信息
DynamicDataSourceContextHolder.clear();

}

public static void serviceA() {
// 在 serviceA 中切换到 dataSource2
DynamicDataSourceContextHolder.push("master");
System.out.println("serviceA 中的数据源: " + DynamicDataSourceContextHolder.peek());

// 调用 serviceB
serviceB();

// 恢复到上一个数据源
DynamicDataSourceContextHolder.poll();
System.out.println("serviceA 结束后数据源: " + DynamicDataSourceContextHolder.peek());
}

public static void serviceB() {
// 在 serviceB 中切换到 dataSource3
DynamicDataSourceContextHolder.push("tos211");
System.out.println("serviceB 中的数据源: " + DynamicDataSourceContextHolder.peek());

// 调用完成后恢复到上一个数据源
DynamicDataSourceContextHolder.poll();
System.out.println("serviceB 结束后数据源: " + DynamicDataSourceContextHolder.peek());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
最终截图如下:


————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_47872288/article/details/138679181