Spring Boot动态数据源实现详解

一、为什么需要动态数据源

在项目开发中,我们经常需要使用多个数据源,比如主库和从库的读写分离,或者不同的租户使用不同的数据源等。如果每个数据源都需要手动配置,那么会增加很多不必要的工作量,而且代码会变得冗长和难以维护。这时候,动态数据源就可以派上用场。

动态数据源就是可以在程序运行时根据需要动态地切换数据库连接的数据源。它可以减少不必要的配置工作,提高代码的可维护性和可扩展性。

二、如何实现动态数据源

1. 自定义数据源

首先,我们需要定义一个数据源的抽象,具体实现交给具体的数据源来完成。下面是一个简单的抽象示例:

public abstract class AbstractRoutingDataSource extends AbstractDataSource {

    /**
     * 获取当前线程上的数据源路由
     *
     * @return 数据源路由,如果为空,则默认返回默认数据源
     */
    protected abstract Object determineCurrentDataSourceKey();

    @Override
    public Connection getConnection() throws SQLException {
        // 获取数据源
        DataSource dataSource = determineCurrentDataSource();
        // 获取连接
        Connection connection = dataSource.getConnection();
        // 返回连接
        return connection;
    }

    /**
     * 获取当前数据源
     *
     * @return 当前数据源
     */
    private DataSource determineCurrentDataSource() {
        // 获取数据源路由
        Object dataSourceKey = determineCurrentDataSourceKey();
        // 如果数据源路由为空,则返回默认数据源
        if (dataSourceKey == null) {
            return getDefaultDataSource();
        }
        // 否则返回指定的数据源
        DataSource dataSource = getTargetDataSources().get(dataSourceKey);
        if (dataSource == null) {
            throw new IllegalStateException("Cannot find data source with key: " + dataSourceKey);
        }
        return dataSource;
    }

}

在上面的抽象类中,我们定义了一个determineCurrentDataSourceKey()方法来获取当前线程上的数据源路由,以及determineCurrentDataSource()方法来获取当前数据源。具体的数据源实现需要继承这个抽象类,并实现这两个方法。下面是一个示例:

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Map<String, DataSource> dataSources = new ConcurrentHashMap();

    private DataSource defaultDataSource;

    @Override
    protected Object determineCurrentDataSourceKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }

    @Override
    public void afterPropertiesSet() {
        Map<Object, Object> targetDataSources = new HashMap();
        targetDataSources.put("default", defaultDataSource);
        for (String key : dataSources.keySet()) {
            targetDataSources.put(key, dataSources.get(key));
        }
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 添加数据源
     *
     * @param key       数据源标识
     * @param dataSource 数据源
     */
    public void addDataSource(String key, DataSource dataSource) {
        dataSources.put(key, dataSource);
        logger.info("Add data source [{}]", key);
    }

    /**
     * 设置默认数据源
     *
     * @param defaultDataSource 默认数据源
     */
    public void setDefaultDataSource(DataSource defaultDataSource) {
        this.defaultDataSource = defaultDataSource;
    }

}

在上面的示例中,我们继承了抽象类,并实现了determineCurrentDataSourceKey()方法和afterPropertiesSet()方法。我们还可以添加数据源和设置默认数据源。这样一来,我们就可以动态地添加和切换数据源了。

2. 使用AOP切面切换数据源

接下来,我们需要通过AOP实现数据源的切换。这里我们使用@Around注解,实现在访问数据库前或者后切换数据源。

@Aspect
@Component
public class DataSourceAspect {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 获取方法名
        String methodName = point.getSignature().getName();

        // 获取目标类
        Class targetClass = point.getTarget().getClass();

        // 获取数据源注解
        DataSource dataSource = targetClass.getAnnotation(DataSource.class);

        Method method = ((MethodSignature) point.getSignature()).getMethod();
        if (method.isAnnotationPresent(DataSource.class)) {
            dataSource = method.getAnnotation(DataSource.class);
            methodName = method.getName();
        }

        // 如果有数据源注解,则切换数据源
        if (dataSource != null) {
            String dataSourceKey = dataSource.value();
            DataSourceContextHolder.setDataSourceKey(dataSourceKey);
            logger.info("Switch data source to [{}] for method [{}]", dataSourceKey, methodName);
        }

        // 执行目标方法
        Object result = point.proceed();

        // 切换回默认数据源
        if (dataSource != null) {
            DataSourceContextHolder.clearDataSourceKey();
            logger.info("Restore data source to default for method [{}]", methodName);
        }

        return result;
    }

}

在上面的代码中,我们使用around()方法来拦截类中使用了@DataSource注解的方法,然后切换数据源。注意,我们使用了DataSourceContextHolder来存放当前数据源路由。

三、示例代码

1. 定义注解

我们需要定义一个@DataSource注解来标记使用哪个数据源:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "default";
}

2. 数据源切换

我们使用DynamicRoutingDataSource来实现数据源的添加和切换:

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.default")
    public DataSource defaultDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.test")
    public DataSource testDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.demo")
    public DataSource demoDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DynamicRoutingDataSource dynamicRoutingDataSource() {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setDefaultDataSource(defaultDataSource());

        dataSource.addDataSource("test", testDataSource());
        dataSource.addDataSource("demo", demoDataSource());

        return dataSource;
    }

    /**
     * 设置动态数据源
     *
     * @param dataSource 动态数据源
     * @return 动态数据源
     */
    @Bean
    public DataSource dataSource(DynamicRoutingDataSource dataSource) {
        return dataSource;
    }

}

注意,我们使用@ConfigurationProperties注解来自动加载数据源配置,然后使用DynamicRoutingDataSource将配置转换为数据源,并添加到数据源路由中。

3. 测试数据源切换

最后,我们来测试一下数据源切换是否生效。首先,我们需要使用@DataSource注解来指定使用哪个数据源:

@Mapper
public interface UserMapper {

    @Select("select id, name from user limit 1")
    @DataSource("test")
    User getTestUser();

    @Select("select id, name from user limit 1")
    @DataSource("demo")
    User getDemoUser();

}

然后,在UserService中调用UserMapper

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public User getTestUser() {
        return userMapper.getTestUser();
    }

    public User getDemoUser() {
        return userMapper.getDemoUser();
    }

}

最后,在测试用例中验证数据源是否正确切换:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testSwitchDataSource() {
        User testUser = userService.getTestUser();
        assertNotNull(testUser);

        User demoUser = userService.getDemoUser();
        assertNotNull(demoUser);
    }

}

如果测试用例可以正常运行,那么就说明数据源切换成功了。

四、总结

通过本文的介绍,我们学习了如何使用Spring Boot动态数据源,使得程序可以动态地切换不同的数据库连接。具体实现需要使用AOP切面和自定义数据源来实现。相信大家已经对动态数据源有了一定的了解,并可以根据需要自己实现动态数据源。

原创文章,作者:JXAMT,如若转载,请注明出处:https://www.506064.com/n/333999.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
JXAMTJXAMT
上一篇 2025-02-05 13:05
下一篇 2025-02-05 13:05

相关推荐

  • QML 动态加载实践

    探讨 QML 框架下动态加载实现的方法和技巧。 一、实现动态加载的方法 QML 支持从 JavaScript 中动态指定需要加载的 QML 组件,并放置到运行时指定的位置。这种技术…

    编程 2025-04-29
  • Spring Boot 集成 Jacoco

    本文将从以下几个方面介绍如何在 Spring Boot 中集成 Jacoco:1、Jacoco 概述;2、Spring Boot 集成 Jacoco 的配置;3、生成 Jacoco…

    编程 2025-04-29
  • Spring Boot中发GET请求参数的处理

    本文将详细介绍如何在Spring Boot中处理GET请求参数,并给出完整的代码示例。 一、Spring Boot的GET请求参数基础 在Spring Boot中,处理GET请求参…

    编程 2025-04-29
  • Python爱心代码动态

    本文将从多个方面详细阐述Python爱心代码动态,包括实现基本原理、应用场景、代码示例等。 一、实现基本原理 Python爱心代码动态使用turtle模块实现。在绘制一个心形的基础…

    编程 2025-04-29
  • 如何在Spring Cloud中整合腾讯云TSF

    本篇文章将介绍如何在Spring Cloud中整合腾讯云TSF,并提供完整的代码示例。 一、TSF简介 TSF (Tencent Serverless Framework)是腾讯云…

    编程 2025-04-29
  • 如何使用Spring Boot ElasticJob进行配置覆盖

    本文将详细介绍如何使用Spring Boot ElasticJob进行配置覆盖。 一、目录结构 我们需要准备两个目录,分别是“elastic-job-lite-spring-boo…

    编程 2025-04-28
  • Spring Boot中使用DTO、Controller、Service、Mapper进行开发

    本文将介绍如何在Spring Boot中使用DTO、Controller、Service、Mapper等技术进行开发。 一、DTO DTO(Data Transfer Object…

    编程 2025-04-28
  • t3.js:一个全能的JavaScript动态文本替换工具

    t3.js是一个非常流行的JavaScript动态文本替换工具,它是一个轻量级库,能够很容易地实现文本内容的递增、递减、替换、切换以及其他各种操作。在本文中,我们将从多个方面探讨t…

    编程 2025-04-28
  • 使用easypoi创建多个动态表头

    本文将详细介绍如何使用easypoi创建多个动态表头,让表格更加灵活和具有可读性。 一、创建单个动态表头 easypoi是一个基于POI操作Excel的Java框架,支持通过注解的…

    编程 2025-04-28
  • Spring S_CSRF防护机制实现及应用

    Spring S_CSRF防护机制是Spring Security框架提供的一个针对跨站请求伪造攻击(CSRF)的保护机制。本文将从以下几个方面详细介绍Spring S_CSRF防…

    编程 2025-04-28

发表回复

登录后才能评论