博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
DeepSeek-行业融合之万象视界(附实战案例详解100+)
全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
感兴趣的可以先收藏起来,希望帮助更多的人
在现代的软件开发中,一个应用系统往往需要与多个数据源进行交互。例如,为了实现读写分离,我们会将读操作和写操作分别指向不同的数据库;或者在进行微服务架构设计时,不同的服务可能会使用不同的数据库。Spring Boot作为一款流行的Java开发框架,为我们提供了便捷的多数据源配置方式。同时,在涉及多个数据源的操作时,如何进行动态切换以及处理分布式事务是我们需要解决的重要问题。本文将详细介绍Spring Boot中多数据源的配置、动态切换以及分布式事务的处理方法。
首先,我们需要在application.properties
或application.yml
文件中配置多个数据源的信息。以下是一个application.yml
的示例:
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/db1
username: root
password: password1
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/db2
username: root
password: password2
driver-class-name: com.mysql.cj.jdbc.Driver
接下来,我们需要创建数据源配置类,将配置文件中的数据源信息加载到Spring容器中。以下是一个简单的数据源配置类示例:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
}
在上述代码中,我们使用@Primary
注解标记了主数据源,同时使用@ConfigurationProperties
注解将配置文件中的数据源信息绑定到数据源对象上。
为了实现动态数据源的切换,我们需要创建一个动态数据源上下文管理器,用于保存当前线程使用的数据源信息。以下是一个简单的动态数据源上下文管理器示例:
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
接下来,我们需要创建一个动态数据源类,继承自AbstractRoutingDataSource
,并重写determineCurrentLookupKey
方法,用于根据当前线程的数据源信息动态选择数据源。以下是一个简单的动态数据源实现示例:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}
在数据源配置类中,我们需要将动态数据源配置到Spring容器中。以下是修改后的数据源配置类示例:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
DynamicDataSource dynamicDataSource = new DynamicDataSource(primaryDataSource, targetDataSources);
return dynamicDataSource;
}
}
在需要切换数据源的地方,我们可以使用DynamicDataSourceContextHolder
来设置当前线程使用的数据源。以下是一个简单的示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertUserToPrimary() {
DynamicDataSourceContextHolder.setDataSource("primary");
jdbcTemplate.update("INSERT INTO users (name) VALUES ('John')");
DynamicDataSourceContextHolder.clearDataSource();
}
public void insertUserToSecondary() {
DynamicDataSourceContextHolder.setDataSource("secondary");
jdbcTemplate.update("INSERT INTO users (name) VALUES ('Jane')");
DynamicDataSourceContextHolder.clearDataSource();
}
}
在涉及多个数据源的操作时,我们需要确保这些操作的原子性,即要么全部成功,要么全部失败。这就需要使用分布式事务处理机制。常见的分布式事务解决方案有两阶段提交(2PC)、三阶段提交(3PC)、TCC(Try-Confirm-Cancel)和基于消息的最终一致性等。
Seata是一个开源的分布式事务解决方案,提供了AT、TCC、SAGA和XA四种事务模式。以下是使用Seata的AT模式处理分布式事务的步骤:
在pom.xml
文件中引入Seata相关依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<version>2.2.6.RELEASEversion>
dependency>
在application.yml
文件中配置Seata相关信息:
seata:
enabled: true
application-id: my-application
tx-service-group: my_tx_group
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
创建一个全局事务管理器配置类:
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SeataConfig {
@Bean
public GlobalTransactionScanner globalTransactionScanner() {
return new GlobalTransactionScanner("my-application", "my_tx_group");
}
}
在需要使用分布式事务的方法上添加@GlobalTransactional
注解:
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DistributedTransactionService {
@Autowired
private UserService userService;
@GlobalTransactional
public void insertUsers() {
userService.insertUserToPrimary();
userService.insertUserToSecondary();
}
}
通过本文的介绍,我们了解了Spring Boot中多数据源的配置方法、动态数据源的切换实现以及分布式事务的处理方案。在实际开发中,我们可以根据具体的业务需求选择合适的数据源配置和分布式事务处理方式。同时,我们还需要注意数据源的性能优化和分布式事务的异常处理,以确保系统的稳定性和可靠性。