谈谈Spring Boot 数据源加载及其多数据源简单实现

简介: 业务需求提供所有微服务数据源的图形化维护功能代码生成可以根据选择的数据源加载表等源信息数据源管理要支持动态配置,实时生效附录效果图实现思路本文提供方法仅供类似简单业务场景,在生产环境和复杂的业务场景 请使用分库分表的中间件(例如mycat)或者框架 sharding-sphere (一直在用)等先来看Spring 默认的数据源注入策略,如下代码默认的事务管理器在初始化时回去加载数据源实现。

业务需求

  • 提供所有微服务数据源的图形化维护功能
  • 代码生成可以根据选择的数据源加载表等源信息
  • 数据源管理要支持动态配置,实时生效
    附录效果图


实现思路

本文提供方法仅供类似简单业务场景,在生产环境和复杂的业务场景 请使用分库分表的中间件(例如mycat)或者框架 sharding-sphere (一直在用)等

  • 先来看Spring 默认的数据源注入策略,如下代码默认的事务管理器在初始化时回去加载数据源实现。这里就是我们动态数据源的入口
// 默认的事务管理器
ppublic class DataSourceTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, InitializingBean {
    
    // 启动时候注入一个数据源
    public void setDataSource(@Nullable DataSource dataSource) {
        if (dataSource instanceof TransactionAwareDataSourceProxy) {
            this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
        }
        else {
            this.dataSource = dataSource;
        }
    }
」
  • 通过注入一个新的DataSourceTransactionManager 实现,并且给它设置多个 DataSource 来实现多数据源实现
  • 看下Spring 默认提供的路由数据源字段
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    
    // 用户设置的全部的数据源配置
    @Nullable
    private Map<Object, Object> targetDataSources;
    // 为空默认的数据源配置
    @Nullable
    private Object defaultTargetDataSource;
    
    // 路由键查找实现
    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    
    // 最终有效的数据源配置(一般清空对应上边用户的设置)
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;
}

开始动手

  • 实现AbstractRoutingDataSource,定一个动态数据源实现,只需要实现他的路由key 查找方法即可。
    这里的路由key 对应其实是resolvedDataSources Map 的key哟
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 指定路由Key,这里很简单 获取 threadLocal 中目标key 即可
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
  • 把我们动态数据源实现注入到Spring 的事务管理器,去数据库查询出来全部的数据源信息,定义一个个具体的数据源实现 我这里使用的HikariDataSource 给他赋值等等
@Slf4j
@Configuration
@AllArgsConstructor
public class DynamicDataSourceConfig implements TransactionManagementConfigurer {
    private final Map<Object, Object> dataSourceMap = new HashMap<>(8);
    private final DataSourceProperties dataSourceProperties;

    @Bean("dynamicDataSource")
    public DynamicDataSource dataSource() {
        JdbcTemplate(dds).queryForList(DataSourceConstant.QUERY_DS_SQL);
        log.info("开始 -> 初始化动态数据源");
        Optional.of(dbList).ifPresent(list -> list.forEach(db -> {
            log.info("数据源:{}", db.get(DataSourceConstant.DS_NAME));
            HikariDataSource ds = new HikariDataSource();
            dataSourceMap.put(db.get(DataSourceConstant.DS_ROUTE_KEY), ds);
        }));
        
        DynamicDataSource ds = new DynamicDataSource();
        ds.setTargetDataSources(dataSourceMap);
        return ds;
    }

    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager();
    }

}

怎么使用

只需要根据用户前台选择的数据源key ,在业务类保存到TTL 即可,会自动根据选择路由数据源

DynamicDataSourceContextHolder.setDataSourceType(key)

这里当然也可以根据AOP 自定义注解等实现。

如何动态数据源动态配置

上边其实已经完成了 我们想要的需求功能,但是有什么问题呢?
我们在数据源管理面维护了数据源,动态去修改这个 dataSourceMap 其实是无效的,不能做到实时刷新

我们来看下 AbstractRoutingDataSource 的加载map 数据源的源码,只有在初始化的时候调用 afterPropertiesSet 去初始数据源map.

那我们只要获取当前的DynamicDataSource bean 手动调用afterPropertiesSet 即可。
整个代码如下

public class DynamicDataSourceConfig implements TransactionManagementConfigurer {
    private final Map<Object, Object> dataSourceMap = new HashMap<>(8);
    private final DataSourceProperties dataSourceProperties;
    private final StringEncryptor stringEncryptor;

    @Bean("dynamicDataSource")
    public DynamicDataSource dataSource() {
        DynamicDataSource ds = new DynamicDataSource();
        HikariDataSource cads = new HikariDataSource();
        cads.setJdbcUrl(dataSourceProperties.getUrl());
        cads.setDriverClassName(dataSourceProperties.getDriverClassName());
        cads.setUsername(dataSourceProperties.getUsername());
        cads.setPassword(dataSourceProperties.getPassword());
        ds.setDefaultTargetDataSource(cads);
        dataSourceMap.put(0, cads);
        ds.setTargetDataSources(dataSourceMap);
        return ds;
    }

    /**
     * 组装默认配置的数据源,查询数据库配置
     */
    @PostConstruct
    public void init() {
        DriverManagerDataSource dds = new DriverManagerDataSource();
        dds.setUrl(dataSourceProperties.getUrl());
        dds.setDriverClassName(dataSourceProperties.getDriverClassName());
        dds.setUsername(dataSourceProperties.getUsername());
        dds.setPassword(dataSourceProperties.getPassword());

        List<Map<String, Object>> dbList = new JdbcTemplate(dds).queryForList(DataSourceConstant.QUERY_DS_SQL);
        log.info("开始 -> 初始化动态数据源");
        Optional.of(dbList).ifPresent(list -> list.forEach(db -> {
            log.info("数据源:{}", db.get(DataSourceConstant.DS_NAME));
            HikariDataSource ds = new HikariDataSource();
            ds.setJdbcUrl(String.valueOf(db.get(DataSourceConstant.DS_JDBC_URL)));
            ds.setDriverClassName(Driver.class.getName());
            ds.setUsername((String) db.get(DataSourceConstant.DS_USER_NAME));

            String decPwd = stringEncryptor.decrypt((String) db.get(DataSourceConstant.DS_USER_PWD));
            ds.setPassword(decPwd);
            dataSourceMap.put(db.get(DataSourceConstant.DS_ROUTE_KEY), ds);
        }));

        log.info("完毕 -> 初始化动态数据源,共计 {} 条", dataSourceMap.size());
    }

    /**
     * 重新加载数据源配置
     */
    public Boolean reload() {
        init();
        DynamicDataSource dataSource = dataSource();
        dataSource.setTargetDataSources(dataSourceMap);
        dataSource.afterPropertiesSet();
        return Boolean.FALSE;
    }


    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager();
    }

总结

欢迎关注我们获得更多的好玩JavaEE 实践

目录
相关文章
|
29天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
233 17
Spring Boot 两种部署到服务器的方式
|
3月前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
200 8
|
3月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
79 2
|
3月前
|
Java 数据库连接
SpringBoot配置多数据源实战
第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision
86 8
|
4月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
4月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
139 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
4月前
|
Java Shell C++
Springboot加载注入bean的方式
本文详细介绍了Spring Boot中Bean的装配方法。首先讲解了使用@Component、@Service、@Controller、@Repository等注解声明Bean的方式,并解释了这些注解之间的关系及各自适用的层次。接着介绍了通过@Configuration和@Bean注解定义Bean的方法,展示了其灵活性和定制能力。最后讨论了@Component与@Bean的区别,并提供了在Spring Boot应用中装配依赖包中Bean的三种方法:使用@ComponentScan注解扫描指定包、使用@Import注解导入特定Bean以及在spring.factories文件中配置Bean。
145 0
|
4月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
119 2
|
5月前
|
消息中间件 NoSQL 安全
(转)Spring Boot加载 不同位置的 application.properties配置文件顺序规则
这篇文章介绍了Spring Boot加载配置文件的顺序规则,包括不同位置的application.properties文件的加载优先级,以及如何通过命令行参数或环境变量来指定配置文件的名称和位置。
165 0
|
5月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)