程序多数据源配置
# 1、什么时候会用到多数据源(Multiple Data Sources)
在Java开发中,“多数据源”指的是在一个应用程序中配置和使用多个不同的数据库连接。通常情况下,一个Java应用程序会连接到一个单一的数据库。然而,在一些复杂的应用场景中,可能需要访问多个不同的数据库,这时就需要配置多数据源。
# 1.1、多数据源具体含义
- 多个数据库实例:多数据源可以是指同一个类型(如MySQL)的多个数据库实例。例如,一个应用程序可能需要同时连接到两个不同的MySQL数据库,一个用于存储用户信息,另一个用于存储订单信息。
- 不同类型的数据库:多数据源也可以是指不同类型的数据库。例如,一个应用程序可能需要连接一个MySQL数据库来存储用户信息,同时还需要连接一个Oracle数据库来存储财务数据。
- 不同的访问策略:多数据源配置也可能用于实现数据库访问策略的不同,比如读写分离(一个数据源用于写操作,另一个用于读操作),或者是在多租户架构下,不同租户使用不同的数据源。
# 1.2、为什么需要多数据源
- 业务需求:不同的业务模块可能需要存储在不同的数据库中。例如,财务数据和用户数据可能分别存储在两个独立的数据库中,以便更好地进行管理和安全控制。
- 系统架构:在大型分布式系统中,通常会使用分库分表来应对海量数据的挑战,不同的数据库实例可能分布在不同的服务器上。
- 读写分离:为了提高系统的性能,尤其是在高并发场景下,常见的做法是将数据库的读操作和写操作分开,读操作可以从多个只读的从库(Slave)中获取数据,而写操作则写入到主库(Master)。
- 迁移或兼容性:在系统迁移或升级的过程中,可能需要同时访问新旧两个数据库系统,或者需要兼容不同版本的数据库。
- 多租户支持:在SaaS应用中,每个租户的数据可能被隔离存储在不同的数据库中,以确保数据的独立性和安全性。
# 2、Java中实现多数据源的三种方式
# 2.1 基于Spring提供的AbstractRoutingDataSource
使用 Spring 提供的 AbstractRoutingDataSource 实现多数据源配置是一种动态数据源管理的有效方法。通过继承 AbstractRoutingDataSource,可以根据某些条件(如请求上下文、当前线程信息等)动态地选择要使用的数据源。这种方式非常适合需要根据业务逻辑动态切换数据源的场景,如读写分离、分库分表等。
# 2.1.1 创建一个 DynamicDataSource 类
首先,创建一个继承 AbstractRoutingDataSource 的类,来实现数据源的动态切换逻辑。
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
// 当前使用的数据源标识
public static ThreadLocal<String> name=new ThreadLocal<>();
// 写数据源
@Autowired
DataSource dataSource1;
// 读数据源
@Autowired
DataSource dataSource2;
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
@Override
public void afterPropertiesSet() {
// 为targetDataSources初始化所有数据源
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put("W",dataSource1);
targetDataSources.put("R",dataSource2);
super.setTargetDataSources(targetDataSources);
// 为defaultTargetDataSource 设置默认的数据源
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet();
}
}
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
# 2.1.2 配置数据源
接下来,我们需要配置多个数据源。
@Configuration
public class DataSourceConfig {
/**
*数据库1的配置
**/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
*数据库2的配置
**/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
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
# 2.1.3 自定义注解,便于区别不同数据源
在业务层,我们可以通过注解设置当前数据源的标识符来实现数据源的动态切换。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {
String value() default "W";
}
2
3
4
5
在业务层,我们可以通过注解设置当前数据源的标识符来实现数据源的动态切换。将自己实现的DynamicDataSource注册成为默认的DataSource实例后,只需要在每次使用DataSource时,提前改变一下其中的name标识,就可以快速切换数据源。
@Component
@Aspect
public class DynamicDataSourceAspect implements Ordered {
// 前置
@Before("within(org.arkham.dynamic_datasource.service.impl.*) && @annotation(wr)")
public void before(JoinPoint point, WR wr){
String name = wr.value();
DynamicDataSource.name.set(name);
}
@Override
public int getOrder() {
return 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.1.4 Service通过注解设置数据源
在业务层具体使用方法上增加注解,实现数据源切换
@Service
public class UserImplService implements UserService {
@Autowired
UserMapper userMapper;
@Override
@WR("R") // 库1
public List<User> list() {
return userMapper.list();
}
@Override
@WR("W") // 库2
public void save(User friend) {
userMapper.save(friend);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2.2 使用MyBatis注册多个SqlSessionFactory
在Spring 环境中,如果有多个数据源并且需要为每个数据源配置单独的 SqlSessionFactory 和SqlSessionTemplate,可以通过配置多个 SqlSessionFactory 实现这一点。如果使用MyBatis框架,要注册多个数据源的话,就需要将MyBatis底层的DataSource、SqlSessionFactory、DataSourceTransactionManager这些核心对象一并进行手动注册
# 2.2.1 不同数据源注册
数据源1:
@Configuration
// 继承mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.w",
sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory wSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主库
sessionFactory.setDataSource(dataSource1());
// 可以手动指定主库对应的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/order/*.xml"));*/
return sessionFactory.getObject();
}
@Bean
@Primary
public DataSourceTransactionManager wTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource1());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate wTransactionTemplate(){
return new TransactionTemplate(wTransactionManager());
}
}
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
数据源2:
@Configuration
// 继承mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "org.arkham.dynamic_datasource_mybatis.mapper.r",
sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory rSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主库
sessionFactory.setDataSource(dataSource2());
// 可以手动指定主库对应的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/r/*.xml"));*/
return sessionFactory.getObject();
}
@Bean
public DataSourceTransactionManager rTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource2());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate rTransactionTemplate(){
return new TransactionTemplate(rTransactionManager());
}
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
# 2.2.2 业务层具体使用
在应用程序中,具体的 DAO 层使用 SqlSessionFactory 和 SqlSessionTemplate 时,MyBatis 会根据 @MapperScan 指定的包路径,使用相应的数据源。
@Service
public class UserImplService implements UserService {
@Autowired
private RUserMapper rFriendMapper;
@Autowired
private WUserMapper wFriendMapper;
// 读-- 读库
@Override
public List<User> list() {
return rFriendMapper.list();
}
// 保存-- 写库
@Override
public void save(User user) {
wFriendMapper.save(user);
}
// 保存-- 写库
@Override
public void saveW(User user) {
user.setName("xman11");
wFriendMapper.save(user);
}
// 保存-- 读库
@Override
public void saveR(User user) {
user.setName("xman");
rFriendMapper.save(user);
}
}
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
# 2.3 使用dynamic-datasource框架
使用 dynamic-datasource-spring-boot-starter 框架可以非常方便地实现 Java 项目中的多数据源配置。这个框架能够支持多种数据源的自动切换、动态数据源的配置管理等功能。
# 2.3.1 添加依赖
首先,需要在 pom.xml 文件中添加 dynamic-datasource-spring-boot-starter 的依赖。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version> <!-- 请根据项目需要调整版本 -->
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <!-- 或其他数据库驱动 -->
</dependency>
2
3
4
5
6
7
8
9
10
# 2.3.2 配置数据源
在application.yml 文件中配置多个数据源。可以给每个数据源起一个唯一的名称(例如 master 和 slave)
spring:
datasource:
dynamic:
primary: master # 默认数据源
datasource:
master: # 数据源1
url: jdbc:mysql://localhost:3306/db1
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave: # 数据源2
url: jdbc:mysql://192.168.1.100:3306/db2
username: user
password: 789012
driver-class-name: com.mysql.cj.jdbc.Driver
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.3.3 配置数据源
使用 @DS 注解来指定某个方法或类使用特定的数据源。
在 Service 方法 或 Mapper 接口 上通过 @DS 注解指定数据源名称:
@Service
public class UserImplService implements UserService {
@Autowired
UserMapper userMapper;
@Override
@DS("master")// 使用主库
public void save(User user) {
userMapper.save(user);
}
@Override
@DS("slave_1") // 使用从库, 如果按照下划线命名方式配置多个 , 可以指定前缀即可(组名)
public List<User> list() {
return userMapper.list();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对于 MyBatis,dynamic-datasource 同样适用。只需正常配置 MyBatis,并在 Mapper 方法上使用 @DS 注解即可。
import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
@DS("slave") // 整个 Mapper 默认使用 slave 数据源
public interface UserMapper {
@DS("master")
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserFromMaster(int id);
@DS("slave")
@Select("SELECT * FROM user WHERE id = #{id}")
User getUserFromSlave(int id);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dynamic-datasource 框架提供了一种简洁、高效的方式来实现多数据源的配置和动态切换。通过注解方式,可以轻松切换数据源,满足业务中对多数据源的需求。
# 2.3.4 关键注解说明
@DS("数据源名称"):- 可标注在 方法 或 类 上
- 方法级优先级 > 类级优先级
- 支持在 Service、Mapper、Controller 层使用
# 2.3.5 动态切换原理
- 通过 AOP 拦截
@DS注解 - 在执行方法前切换到指定数据源
- 方法执行后恢复默认数据源
# 2.3.6 高级用法
(1) 嵌套切换数据源
@DS("master")
public void process() {
insertA(); // 默认 master
// 切换到 slave 执行查询
slaveQuery();
}
@DS("slave")
public void slaveQuery() {
// 此时使用 slave 数据源
}
2
3
4
5
6
7
8
9
10
11
12
(2) 事务管理,在需要事务的方法上添加 @Transactional,注意:
@DS("master")
@Transactional // 事务管理器需对应数据源
public void transactionalMethod() {
// 事务操作
}
2
3
4
5
# 2.3.7 注意事项
- 避免在同一个事务中切换数据源(事务以第一个数据源为准)
- 多个数据源的数据库类型需相同(如全是 MySQL)
- 无需配置
spring.datasource.url,只需配置dynamic.datasource - 支持 多主多从、读写分离 等复杂场景