当进行web开发的时候,有时候会出现需要调用另一个数据库的需求。这篇文章将教会你如何达到此目的,并提供了一键即用的完整代码给各位尝试。
假设一个场景:你现在担任后端应用研发岗位,并且公司拥有一个很棒的中台体系,其中有A B两个微服务(例如A是淘宝网平台,B是支付宝平台),由于A平台是先研发并没有考虑到之后的新产品会共用user信息,因此user表存储在A服务对应的数据库中。而B服务同样也有自己的数据库,那么如何让B能拿到A的user表中的信息来达到user互通呢?
首先我要说明的是,如果你立即想到了最佳实践方案,说明你已经具备了一些优秀的微服务研发思路,恭喜你;倘若你将继续向下看,我相信你一定会在思维的碰撞与相互印证中有新的收获。
先来思考解决方案:
A和B直接共用一个库? 这是最不合适的方式,因为AB本属于两个产品,合库代表着更高的耦合度以及更不清晰的边界分割。
A和B中抽出一个C服务,并将user表迁移至C,并提供API供A和B使用? 看起来这是一个挺不错的方案,并且很多思想都会有类似的方向引导。我承认,这个解决方案对了一大半:将user服务抽象为独立的service是不错的理念,毕竟user本身就是一个能够进行很大程度抽象的模块。但在提供API这一点上有传输性能、网络延迟,依赖的稳定性等诸多问题。
第二种解决方案是合适的,但需要去优化它,这就是今天要讨论的内容:在同一个项目中配置多数据源。作为开发B项目的工程师的我们,先将C项目的依赖导入进来,那么C项目中仍然在用B的数据源,怎么办?不要怕,赶紧瞧一瞧Springboot 2.x + JPA +Hibernate的多数据源配置方案吧!
首先请允许我安利一波我上传到git的demo项目,它能够经过几个非常简单的改动后直接启动运行(只给代码但是跑不起来这种事我是坚决不能做的)。下面我会对照着代码与大家一起交流多数据源的配置流程。如果你觉得不错,请给我的git项目Star一下,谢谢大家。
Git项目地址: https://github.com/Mr-zyx/multi-db-demo
现在咱们去git上下载好项目,然后跟着我一起学习吧!
git clone https://github.com/Mr-zyx/multi-db-demo.git
这个地方我额外说一下,提供的sql query生成了demo_primary和demo_secondary两个库,第一个库中建了books,users,schools 共3个表,并为其填入了一些数据;第二个库中只建立了books,users 共两个表,并填充了不同的数据。
先看src/main/resources/application.properties
文件,配置如下:
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/demo_primary?useSSL=false&useUnicode=yes&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
spring.datasource.primary.username=root
spring.datasource.primary.password=123456
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/demo_secondary?useSSL=false&useUnicode=yes&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
对比一些正常的单数据源配置:
spring.datasource.url=jdbc:mysql://localhost:3306/demo_primary?useSSL=false&useUnicode=yes&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
区别显而易见:
spring.datasource.url
变成了spring.datasource.xxx.jdbc-url
。 可以看到配置url的后缀变了,这一点是springboot2的配置要求。@Configuration
public class DataSourceConfig {
/***
* 配置主数据源
* @return
*/
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
/****
* 配置2号数据源
* @return
*/
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
注册了两个Bean,目的是使用@ConfigurationProperties
注解寻找到对应前缀的DB相关系统配置。
两个不同的源已被载入程序中,但如何知道哪个repo(dao)调用的是哪个数据源的内容呢?
这是主数据源的配置代码:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.example.repository.**" }) //设置Repository所在位置
public class RepositoryPrimaryConfig {
@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;
@Autowired
private JpaProperties jpaProperties;
@Autowired
private HibernateProperties hibernateProperties;
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(
EntityManagerFactoryBuilder builder) {
//网上文章大多数都是jpaProperties.getHibernateProperties(dataSource);就直接得到了hibernate的配置map,
//但这个方法在springboot2.0+被舍弃了,所以这里改成这样。
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties(), new HibernateSettings());
return builder.dataSource(primaryDataSource).properties(properties)
.packages("com.example.model.**").build();//实体包路径
}
@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}
}
2号数据源代码:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactorySecondary",
transactionManagerRef = "transactionManagerSecondary",
basePackages = {"com.example.repository.secondary"}
) //设置Repository所在位置
public class RepositorySecondaryConfig {
@Autowired
@Qualifier("secondaryDataSource")
private DataSource secondaryDataSource;
@Autowired
private JpaProperties jpaProperties;
@Autowired
private HibernateProperties hibernateProperties;
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(
EntityManagerFactoryBuilder builder) {
//网上文章大多数都是jpaProperties.getHibernateProperties(dataSource);就直接得到了hibernate的配置map,
//但这个方法在springboot2.0+被舍弃了,所以这里改成这样。
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties(), new HibernateSettings());
return builder.dataSource(secondaryDataSource).properties(properties)
.packages("com.example.model.secondary").build();//实体的包路径
}
@Bean(name = "transactionManagerSecondary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
}
}
开始分析这两段代码,可以看出它们的相似度很高,从不相似的地方开始看:
@EnableJpaRepositories
和dataSource.packages("com.example.model.secondary")
里的路径问题。("aa.bb.cc", "mm.nn.*")
spring.main.allow-bean-definition-overriding=true
我提供了几个测试接口供大家使用,以确认配置的正确性:
GET localhost:11000/api/users/primary
GET localhost:11000/api/users/secondary
GET localhost:11000/api/books
GET localhost:11000/api/schools
实践出真知,运行一下看看是不是得到你想要的结果吧!