背景

目前已经是微服务的天下,但是随着业务需求的日益增长,部分应用还是出现了需要同时连接多个数据源操作数据的技术诉求。

需要对现有的技术架构进行优化升级,查阅了下网上的文章,基本都是照搬的同一篇文章,通过代码的方式同时注册primary和second两个数据源。这种实现方案的技术成本比较低,但是维护成本非常高的,如果我需要同时连接4个、5个甚至更多的数据源,需要不断增加代码注册数据源。

实现方案

比较理想的方式,当然是增加些许配置,就能实现数据源的拓展。本文只讨论如果动态注册JPA的数据源,涉及数据源切换等功能大家可以自行实现。

JPA多数据源需要在Spring IOC中注册DataSource、EntityManagerFactory和JpaTransactionManger,具体实现通过BeanDefinitionRegistryPostProcessor进行bean的动态注册,源码如下:

配置项:

spring.datasource.multiple.test.username=root
spring.datasource.multiple.test.password=123456
spring.datasource.multiple.test.url=jdbc:mysql://localhost:3306/tester?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
spring.datasource.multiple.test.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.multiple.test.dialect=org.hibernate.dialect.MySQLDialect
spring.datasource.multiple.test.hikari.pool-name=test-datasource
spring.datasource.multiple.test.hikari.maximum_pool_size=12
#连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
spring.datasource.multiple.test.hikari.connection_timeout=6000
#最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
spring.datasource.multiple.test.hikari.minimum_idle=10
#空闲连接超时时间,默认值600000(10分钟),大于等于max-lifetime且max-lifetime>0,会被重置为0;不等于0且小于10秒,会被重置为10秒。
# 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
spring.datasource.multiple.test.hikari.idle_timeout=600000
#连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
spring.datasource.multiple.test.hikari.max_lifetime=1800000
spring.datasource.multiple.test.hikari.login_timeout=500
spring.datasource.multiple.test.hikari.validation_timeout=1000
spring.datasource.multiple.test.hikari.initialization_fail_timeout=1000
#连接测试查询
spring.datasource.multiple.test.hikari.connection_test_query=SELECT 1

配置类:

public class MultipleDataSource {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private String dialect;

    private HikariConfig hikari;

    public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public String getDriverClassName() {
return driverClassName;
} public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
} public String getDialect() {
return dialect;
} public void setDialect(String dialect) {
this.dialect = dialect;
} public HikariConfig getHikari() {
return hikari;
} public void setHikari(HikariConfig hikari) {
this.hikari = hikari;
} /**
* 数据源配置参数校验
*/
public void validate() {
Assert.hasText(url, "数据库连接地址不能为空");
Assert.hasText(username, "数据库用户名不能为空");
Assert.hasText(password, "数据库密码不能为空");
Assert.hasText(driverClassName, "数据库驱动类不能为空");
Assert.hasText(dialect, "数据库方言不能为空");
Assert.notNull(hikari, "数据库连接池配置不能为空");
} }
public class MultipleDataSourceProperties {

    private Map<String, MultipleDataSource> multiple;

    public Map<String, MultipleDataSource> getMultiple() {
return multiple;
} public void setMultiple(Map<String, MultipleDataSource> multiple) {
this.multiple = multiple;
} /**
* 数据源配置参数校验
*/
public void validate() {
for (Map.Entry<String, MultipleDataSource> entry : multiple.entrySet()) {
entry.getValue().validate();
}
} }

动态注册:

@Slf4j
@Configuration
public class MultipleDataSourceRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ApplicationContextAware { private static final String DATASOURCE_PREFIX = "spring.datasource"; public static final String BEAN_DATASOURCE = "%sDataSource"; public static final String BEAN_ENTITY_MANAGER_FACTORY = "%sEntityManagerFactory"; public static final String BEAN_TRANSACTION_MANAGER = "%sTransactionManager"; protected static final String SCAN_PACKAGE = "com.cestc.*.bean.%s"; protected static final String PERSISTENCE_NAME = "%sPersistenceUnit"; protected final Map<String, Object> jpaProperties = new HashMap<>(8); private Environment environment; private ApplicationContext context; public MultipleDataSourceRegistryPostProcessor() {
jpaProperties.put("current_session_context_class", "org.springframework.orm.hibernate5.SpringSessionContext");
jpaProperties.put("hibernate.show_sql", "true");
jpaProperties.put("hibernate.format_sql", "true");
jpaProperties.put("hibernate.hbm2ddl.auto", "none");
} @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
} /**
* 注册Hikari数据源
* @param dataSource 数据源配置信息
*/
protected BeanDefinition registryDataSource(MultipleDataSource dataSource) { return BeanDefinitionBuilder.genericBeanDefinition(HikariDataSource.class, () -> {
dataSource.getHikari().setJdbcUrl(dataSource.getUrl());
dataSource.getHikari().setUsername(dataSource.getUsername());
dataSource.getHikari().setPassword(dataSource.getPassword());
dataSource.getHikari().setDriverClassName(dataSource.getDriverClassName()); return new HikariDataSource(dataSource.getHikari());
}).getBeanDefinition(); } /**
* 注册EntityManagerFactory
* @param name 数据源名称
* @param dataSource 数据源配置信息
* @param beanFactory
*/
protected BeanDefinition registryEntityManagerFactory(String name, MultipleDataSource dataSource, DefaultListableBeanFactory beanFactory) { HikariDataSource hikariDataSource = beanFactory.getBean(String.format(BEAN_DATASOURCE, name), HikariDataSource.class);
jpaProperties.put("hibernate.dialect", dataSource.getDialect()); return BeanDefinitionBuilder.genericBeanDefinition(LocalContainerEntityManagerFactoryBean.class, () -> {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(hikariDataSource);
entityManagerFactoryBean.setPackagesToScan(String.format(SCAN_PACKAGE, name));
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setJpaPropertyMap(jpaProperties);
entityManagerFactoryBean.setPersistenceUnitName(String.format(PERSISTENCE_NAME, name)); return entityManagerFactoryBean;
}).getBeanDefinition(); } /**
* 注册数据源事务管理器
* @param name
* @param beanFactory
*/
protected BeanDefinition registryTransactionManager(String name, DefaultListableBeanFactory beanFactory) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JpaTransactionManager.class);
builder.addConstructorArgReference(String.format(BEAN_ENTITY_MANAGER_FACTORY, name));
return builder.getBeanDefinition(); } @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; MultipleDataSourceProperties multipleDataSourceProperties = Binder.get(this.environment).bind(DATASOURCE_PREFIX, MultipleDataSourceProperties.class).get(); if (Optional.ofNullable(multipleDataSourceProperties).isPresent()
&& !multipleDataSourceProperties.getMultiple().isEmpty()) { // 校验数据源参数
multipleDataSourceProperties.validate(); for (Map.Entry<String, MultipleDataSource> entry : multipleDataSourceProperties.getMultiple().entrySet()) {
log.info("注册数据源[{}]", entry.getKey()); // 注册数据源
defaultListableBeanFactory.registerBeanDefinition(String.format(BEAN_DATASOURCE, entry.getKey()), registryDataSource(entry.getValue()));
// 注册entityManagerFactory
defaultListableBeanFactory.registerBeanDefinition(String.format(BEAN_ENTITY_MANAGER_FACTORY, entry.getKey()), registryEntityManagerFactory(entry.getKey(), entry.getValue(), defaultListableBeanFactory));
// 注册事务管理器
defaultListableBeanFactory.registerBeanDefinition(String.format(BEAN_TRANSACTION_MANAGER, entry.getKey()), registryTransactionManager(entry.getKey(), defaultListableBeanFactory));
}
} } @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
} @Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}

欢迎交流

JPA动态注册多数据源的更多相关文章

  1. Spring动态注册bean实现动态多数据源

    Spring动态注册bean实现动态多数据源 http://blog.csdn.net/littlechang/article/details/8071882

  2. SpringBoot 动态切换多数据源

    1. 配置文件application-dev.properties 2. 动态切换数据源核心 A. 数据源注册器 B. 动态数据源适配器 C. 自定义注解 D. 动态数据源切面     E. 数据源路 ...

  3. Delphi动态配置ODBC数据源--SQL Server版本

    (摘自)http://jxlearnew.blog.163.com/blog/static/549786592007102451431413/ 这里介绍一种用Delphi来实现动态注册的方法,希望对各 ...

  4. 180804-Spring之动态注册bean

    Spring之动态注册bean 什么场景下,需要主动向Spring容器注册bean呢? 如我之前做个的一个支持扫表的基础平台,使用者只需要添加基础配置 + Groovy任务,就可以丢到这个平台上面来运 ...

  5. springboot基于注解动态配置多数据源以及多数据源的事务统一

    参考文档:https://www.cnblogs.com/zhangboyu/p/7622412.html https://blog.csdn.net/qq_34322777/article/deta ...

  6. RPC原来就是Socket——RPC框架到dubbo的服务动态注册,服务路由,负载均衡演化

    序:RPC就是使用socket告诉服务端我要调你的哪一个类的哪一个方法然后获得处理的结果.服务注册和路由就是借助第三方存储介质存储服务信息让服务消费者调用.然我们自己动手从0开始写一个rpc功能以及实 ...

  7. 你必须知道ASP.NET知识------关于动态注册httpmodule(对不起汤姆大叔)

    一.关于动态注册的问题 很多人看过汤姆大叔的MVC之前的那点事儿系列(6):动态注册HttpModule ,其实汤姆大叔没有发现httpmodule动态注册的根本机制在哪里. 亦即:怎么动态注册?为什 ...

  8. Android开发4: Notification编程基础、Broadcast的使用及其静态注册、动态注册方式

    前言 啦啦啦~(博主每次开篇都要卖个萌,大家是不是都厌倦了呢~) 本篇博文希望帮助大家掌握 Broadcast 编程基础,实现动态注册 Broadcast 和静态注册 Broadcast 的方式以及学 ...

  9. Android只能动态注册的广播Action

    只能动态注册的广播(部分): android.intent.action.SCREEN_ON android.intent.action.SCREEN_OFF android.intent.actio ...

  10. Oracle监听的静态注册和动态注册

    静态注册:通过解析listene.ora文件 动态注册:由PMON进程动态注册至监听中 在没有listener.ora配置文件的情况下,如果启动监听,则监听为动态注册.用图形化netca创建的监听,默 ...

随机推荐

  1. .NET5从零基础到精通:全面掌握.NET5开发技能【第二章】

    章节: 第一章:https://www.cnblogs.com/kimiliucn/p/17613434.html 第二章:https://www.cnblogs.com/kimiliucn/p/17 ...

  2. vite 找不到依赖模块:[plugin:vite:dep-pre-bundle]

    问题描述: 运行项目时,出现[plugin:vite:dep-pre-bundle] 错误.这种问题一般为依赖的包未正常配置相关字段,导致vite无法找到包的入口. 遇到这种模块内.找不到引用模块的, ...

  3. PE文件结构1

    引言 PE文件格式是Windows操作系统下的可执行文件的格式,包括.exe文件和.dll文件,通过PE文件格式的学习,可以帮助我们更加熟悉有关Windows系统下的逆向分析和PC端病毒的学习,同时P ...

  4. 0×03 Vulnhub 靶机渗透总结之 KIOPTRIX: LEVEL 1.2 (#3) SQL注入+sudo提权

    0×03 Vulnhub 靶机渗透总结之 KIOPTRIX: LEVEL 1.2 (#3) 系列专栏:Vulnhub靶机渗透系列 欢迎大佬:点赞️收藏关注 首发时间: 2023年8月22日 如有错误 ...

  5. 《SQL与数据库基础》18. MySQL管理

    目录 MySQL管理 系统数据库 常用工具 mysql mysqladmin mysqlbinlog mysqlshow mysqldump mysqlimport source 本文以 MySQL ...

  6. 如何获取和分析Java堆信息

    引言 在Java应用程序的开发和维护过程中,了解和分析Java堆信息是一项重要的任务.本文将介绍如何获取Java堆信息的不同方法,并提供一些分析堆信息的实用技巧. 获取Java堆信息的方法 Java虚 ...

  7. dotnet SemanticKernel 入门 自定义变量和技能

    本文将告诉大家如何在 SemanticKernel 框架内定义自定义的变量和如何开发自定义的技能 本文属于 SemanticKernel 入门系列博客,更多博客内容请参阅我的 博客导航 自定义变量是一 ...

  8. 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(1)

    在我们的SqlSugar的开发框架中,整合了Winform端.Vue3+ElementPlus的前端.以及基于UniApp+Vue+ThorUI的移动前端几个前端处理,基本上覆盖了我们日常的应用模式了 ...

  9. Java 中for循环和foreach循环哪个更快?

    摘要:本文由葡萄城技术团队于博客园发布.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 在Java编程中,循环结构是程序员常用的控制流程,而for循环 ...

  10. VoIP==Voice over Internet Protocol

    基于IP的语音传输(英语:Voice over Internet Protocol,缩写为VoIP)是一种语音通话技术,经由网际协议(IP)来达成语音通话与多媒体会议,也就是经由互联网来进行通信.其他 ...