Java实战:Spring Boot 实现动态数据源切换

本文将介绍如何在 Spring Boot 应用中实现动态数据源切换,包括具体实现步骤和示例代码。通过本文的学习,你将能够轻松应对多种数据源切换的场景,提高应用程序的灵活性和可扩展性。

一. 引言

在实际开发过程中,我们经常遇到需要同时操作多个数据源的情况。例如,可能需要从不同的数据库中读取数据,或者根据业务需求将数据写入不同的数据库。在这种情况下,动态数据源切换就变得尤为重要。Spring Boot 提供了强大的数据源配置功能,使得动态数据源切换变得相对简单。本文将详细介绍如何在 Spring Boot 应用中实现动态数据源切换。

二. 准备工作

在开始实现动态数据源切换之前,我们需要做一些准备工作:
1.创建 Spring Boot 项目
首先,我们需要创建一个 Spring Boot 项目。可以使用 Spring Initializr(https://start.spring.io/)快速生成项目骨架。在创建项目时,选择合适的 JDK 版本、项目构建工具(如 Maven 或 Gradle)以及项目元数据(如项目名称、描述、包名等)。
2.添加依赖
在项目的 pom.xml 文件中添加以下依赖:

<dependencies>
    <!-- Spring Boot Web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Boot 数据源依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- 数据库驱动依赖,以 MySQL 为例 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

三. 实现动态数据源切换

接下来,我们将介绍如何实现动态数据源切换。主要分为以下几个步骤:
1.配置数据源
在 application.properties 或 application.yml 文件中配置多个数据源,如下所示:

spring:
  datasource:
    primary:
      url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

2.创建数据源配置类
创建一个数据源配置类,用于生成 DataSource 对象。这里我们使用 Java 配置的方式,如下所示:

@Configuration
public class DynamicDataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource dataSource2() {
        return DataSourceBuilder.create().build();
    }
}

3.创建动态数据源路由
创建一个动态数据源路由类,用于根据不同的条件切换不同的数据源。如下所示:

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

4.配置数据源路由
在 application.properties 或 application.yml 文件中配置数据源路由,如下所示:

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    dynamic:
      primary: primaryDataSource
      secondary: secondaryDataSource

5.创建数据源切换切面
创建一个切面类,用于在方法执行前切换数据源,在方法执行后恢复数据源。如下所示:

@Aspect
@Component
public class DynamicDataSourceAspect {
    @Before("@annotation(TargetDataSource)")
    public void switchDataSource(JoinPoint point) {
        // 获取方法上的注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        TargetDataSource ds = method.getAnnotation(TargetDataSource.class);
        // 判断注解是否为空
        if (ds == null) {
            return;
        }
        // 切换数据源
        DynamicDataSourceContextHolder.setDataSourceType(ds.name());
    }
    @After("@annotation(TargetDataSource)")
    public void restoreDataSource(JoinPoint point) {
        // 获取方法上的注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        TargetDataSource ds = method.getAnnotation(TargetDataSource.class);
        // 判断注解是否为空
        if (ds == null) {
            return;
        }
        // 恢复数据源
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

6.创建自定义注解
创建一个自定义注解,用于标记需要切换数据源的方法。如下所示:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String name();
}

7.使用自定义注解
在需要切换数据源的方法上使用自定义注解,如下所示:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @TargetDataSource(name = "primary")
    public List<User> findUsersFromPrimary() {
        return userMapper.findAll();
    }
    @TargetDataSource(name = "secondary")
    public List<User> findUsersFromSecondary() {
        return userMapper.findAll();
    }
}

四. 测试动态数据源切换

为了验证我们的动态数据源切换是否生效,我们可以编写一些测试用例来模拟不同的数据源切换场景。
首先,我们需要创建一个简单的实体类和对应的仓库接口:

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    // 省略 getter 和 setter
}
public interface UserRepository extends JpaRepository<User, Long> {
}

然后,我们可以编写一个测试类来测试动态数据源的切换:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testDynamicDataSource() {
        List<User> usersFromPrimary = userService.findUsersFromPrimary();
        List<User> usersFromSecondary = userService.findUsersFromSecondary();
        
        // 假设我们期望从主数据源和从数据源分别查询到不同的用户列表
        assertNotNull(usersFromPrimary);
        assertNotNull(usersFromSecondary);
        assertNotEquals(usersFromPrimary, usersFromSecondary);
    }
}

在这个测试类中,我们注入了 UserService,并调用了两个方法来分别从主数据源和从数据源查询用户列表。我们使用了 assertNotNullassertNotEquals 来验证查询结果,确保我们从两个不同的数据源获取到了不同的数据。

五. 注意事项

在实现动态数据源切换时,有几个注意事项需要牢记:

  • 确保所有的数据源配置都是正确的,包括URL、用户名、密码和驱动类名。
  • 在切换数据源时,要确保在操作完成后恢复到默认数据源,以避免影响其他业务逻辑。
  • 在生产环境中,要考虑数据源切换的性能影响,尤其是在高并发场景下。
  • 动态数据源切换可能会增加代码的复杂性,因此要谨慎使用,确保它带来的好处大于维护成本。

六. 总结

本文详细介绍了在 Spring Boot 应用中实现动态数据源切换的方法。通过配置多个数据源、创建数据源路由、使用切面编程和自定义注解,我们可以在运行时根据业务需求灵活地切换数据源。这种方法特别适合于需要同时访问多个数据库或数据源的应用场景。虽然动态数据源切换增加了系统的复杂性,但正确使用它可以为应用带来更高的灵活性和可扩展性。
通过本文的学习,您应该能够掌握在 Spring Boot 中实现动态数据源切换的基本技巧。希望本文对您有所帮助,如果您有任何疑问或建议,请随时留言交流。

Spring Boot应用中使用多数据源可以实现读写分离,提高系统的性能和可用性,同时也可以满足不同业务场景下的需求。下面我们来介绍如何使用Spring Boot数据源实现读写分离。 1. 添加依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> ``` 其中,spring-boot-starter-jdbc是Spring Boot提供的JDBC Starter,用于连接数据库。druid是阿里巴巴开源的数据库连接池。 2. 配置数据源 在application.yml文件中添加数据源配置: ``` spring: datasource: master: url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver ``` 其中,我们配置了两个数据源:master和slave。分别连接了两个不同的数据库,用于实现读写分离。 3. 配置数据源和事务管理器 在Spring Boot中,我们需要自己配置数据源和事务管理器。可以在代码中通过@Primary和@Qualifier注解实现数据源动态切换。 ``` @Configuration public class DataSourceConfig { @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DynamicDataSource.DataSourceType.MASTER, masterDataSource); targetDataSources.put(DynamicDataSource.DataSourceType.SLAVE, slaveDataSource); return new DynamicDataSource(masterDataSource, targetDataSources); } @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } } ``` 其中,我们定义了DataSourceConfig配置类,用于配置数据源和事务管理器。我们通过@Bean注解创建数据源对象并读取application.yml配置文件中的数据源信息。然后通过@Primary注解指定默认的数据源为Master数据源。最后,我们创建了DynamicDataSource对象,用于动态切换数据源,同时创建了事务管理器对象,用于管理事务。 4. 定义数据源切换注解 我们可以通过自定义注解来实现动态切换数据源。 ``` @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TargetDataSource { DynamicDataSource.DataSourceType value() default DynamicDataSource.DataSourceType.MASTER; } ``` 其中,@TargetDataSource注解用于标记数据源类型,我们通过value()方法指定数据源类型,默认为Master数据源。 5. 定义动态数据源 我们可以通过继承AbstractRoutingDataSource类来实现动态数据源切换。 ``` public class DynamicDataSource extends AbstractRoutingDataSource { private DataSource masterDataSource; private Map<Object, Object> targetDataSources; public DynamicDataSource(DataSource masterDataSource, Map<Object, Object> targetDataSources) { this.masterDataSource = masterDataSource; this.targetDataSources = targetDataSources; super.setDefaultTargetDataSource(masterDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } public enum DataSourceType { MASTER, SLAVE } } ``` 其中,我们定义了DynamicDataSource类,继承了AbstractRoutingDataSource类。在DynamicDataSource类中,我们重写了determineCurrentLookupKey()方法,根据当前线程的数据源类型,返回对应的数据源。我们通过枚举类型DataSourceType定义了Master和Slave两种数据源类型。 6. 实现数据源切换 我们可以通过AOP的方式,在方法执行前切换数据源。 ``` @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.demo.annotation.TargetDataSource)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); TargetDataSource dataSource = signature.getMethod().getAnnotation(TargetDataSource.class); if (dataSource == null) { DataSourceContextHolder.setDataSourceType(DynamicDataSource.DataSourceType.MASTER); } else { DataSourceContextHolder.setDataSourceType(dataSource.value()); } try { return point.proceed(); } finally { DataSourceContextHolder.clearDataSourceType(); } } } ``` 其中,我们定义了DataSourceAspect切面类,用于切换数据源。我们通过@Around注解标记了切点,在方法执行前,根据注解中指定的数据源类型,切换数据源。执行完毕后,再切换回默认的Master数据源。 7. 编写业务代码 最后,我们编写业务代码,通过@TargetDataSource注解指定数据源类型。 ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @TargetDataSource(DynamicDataSource.DataSourceType.MASTER) public void addUser(User user) { userMapper.addUser(user); } @Override @TargetDataSource(DynamicDataSource.DataSourceType.SLAVE) public User getUserById(int id) { return userMapper.getUserById(id); } } ``` 其中,我们定义了UserServiceImpl类,实现了UserService接口。在addUser()方法中,我们指定数据源类型为Master数据源,在getUserById()方法中,我们指定数据源类型为Slave数据源。 通过以上步骤,我们就可以实现Spring Boot数据源的读写分离了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值