注:代码部分因为影响阅读我将它们折叠起来惹,注意前面有小箭头的文本嗷
本文代码篇幅较长,我愿意写,你愿意听看嘛?
P.S.
实现过程
中喵的用了三天时间才完成这个模块...kotlin害龙不浅呐(小声bb)
对于这部分代码我计划加上其它功能后封装一下,作为一个模版项目开源,不过短时间里并没有足够的时间去做它。
在写了对方三个管理系统之后,展开了一次新的关于数据整合的业务,在这个业务中,我们需要拿到多个项目后台的数据集。 在这里我想到了两种解决方案,分别介绍一下其优劣。
业务后台
,将整合数据使用的后台称为数据后台
既然是做了数据的整合,对于多数据库的访问就是必不可少的了,接下来的就是这篇文章的正题。
SpringBoot配置数据库有两个阶段(2、3):
在这里,分为两个步骤实现,第一步实现通用方法,第二步是实现分库配置的方法
P.S.
先上项目结构,快速认清局势(为了生成一个树状图,专门下了个brew,各种恶心的问题...):
origin # 因为前面的一堆东西太长,干扰视线,所以也就没有加进去了,你们能明白就行
├── config
│ ├── DataSourceConfig.kt # 入口文件,这里用了Druid
│ ├── ServiceAConfig.kt # 业务A使用的数据库配置
│ └── ServiceBConfig.kt # 业务B使用的数据库配置
├── serviceA
│ └── dao
│ └── ServiceADao.kt # 这是个Dao,不用我解释了吧?
└── serviceB
└── dao
└── ServiceBDao.kt # 我记得他们写JPA的喜欢命名为Repository??
// DataSourceConfig.kt
@Configuration
class DataSourceConfig {
@Primary // 默认数据库要加Primary关键词修饰
@Bean("serviceADataSource") // Bean名称,还是起一下的好
@Qualifier("serviceADataSource") // 数据源的分类标记(就像公狗在树下撒尿)
// yml or properties下的配置内容,将内容通过控制中心直接注入
@ConfigurationProperties(prefix = "spring.datasource.serviceA")
fun serviceADataSource(): DataSource {
return DruidDataSource()
// 这里,如果用原生的数据库的话,用下面注释掉的内容即可(我在下面的properties配置中仅配置了Druid的写法,原生的需要你自己去改写)
// return DataSourceBuilder.create().build()
}
@Bean("serviceBDataSource")
@Qualifier("serviceBDataSource")
@ConfigurationProperties(prefix = "spring.datasource.serviceB")
fun serviceBDataSource(): DataSource {
return DruidDataSource()
}
}
// ServiceAConfig.kt
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "serviceAEntityManagerFactory",
transactionManagerRef = "serviceATransactionManager",
basePackages = ["com.arunoido.origin.serviceA"] // 这里是数据库指向的包名,我这里用的是我自己的包名。愿意的话你可以具体指向到自己的Dao层([com.arunoido.origin.serviceA.dao])
)
class ServiceAConfig {
@Autowired
@Qualifier("serviceADataSource")
private lateinit var dataSource: DataSource
@Primary
@Bean(name = ["serviceAEntityManager"])
fun entityManager(builder: EntityManagerFactoryBuilder): EntityManager? {
return entityManagerFactory(builder).getObject()?.createEntityManager()
}
@Primary
@Bean(name = ["serviceAEntityManagerFactory"])
fun entityManagerFactory(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
return builder
.dataSource(dataSource)
.packages("com.arunoido.origin.serviceA.model") // 设置实体类所在位置
.build()
}
@Primary
@Bean(name = ["serviceATransactionManager"])
fun transactionManager(builder: EntityManagerFactoryBuilder): PlatformTransactionManager? {
return JpaTransactionManager(entityManagerFactory(builder).getObject()!!)
}
}
// ServiceBConfig.kt
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "serviceBEntityManagerFactory",
transactionManagerRef = "serviceBTransactionManager",
basePackages = ["com.arunoido.origin.serviceB"] // 可以指向多个包名,你懂的
)
class ServiceBConfig {
@Autowired
@Qualifier("serviceBDataSource")
private lateinit var dataSource: DataSource
@Bean(name = ["serviceBEntityManager"])
fun entityManager(builder: EntityManagerFactoryBuilder): EntityManager? {
return entityManagerFactory(builder).getObject()?.createEntityManager()
}
@Bean(name = ["serviceBEntityManagerFactory"])
fun entityManagerFactory(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
return builder
.dataSource(dataSource)
.packages("com.arunoido.origin.serviceB.model")
.build()
}
@Bean(name = ["serviceBTransactionManager"])
fun transactionManager(builder: EntityManagerFactoryBuilder): PlatformTransactionManager? {
return JpaTransactionManager(entityManagerFactory(builder).getObject()!!)
}
}
配置文件:
└── resources
├── application-dev.properties
└── application.properties
P.S.
spring.profiles.active=dev
# ServiceA的数据库
spring.datasource.serviceA.url=jdbc:mysql://ipaddress:port/serviceA?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.serviceA.username=username
spring.datasource.serviceA.password=pwd
spring.datasource.serviceA.driver-class-name=com.mysql.cj.jdbc.Driver
# ServiceB的数据库
spring.datasource.serviceB.url=jdbc:mysql://ipaddress:port/serviceB?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.serviceB.username=username
spring.datasource.serviceB.password=pwd
spring.datasource.serviceB.driver-class-name=com.mysql.cj.jdbc.Driver
# 通用的JPA配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# Druid的配置,如果不用Druid的话自己配置一下
# 初始化大小,最小,最大
spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=10
#配置获取连接等待超时的时间
spring.datasource.druid.max-wait=60000
#打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=1000
spring.datasource.druid.filter.stat.merge-sql=false
spring.datasource.druid.filter.wall.config.multi-statement-allow=true
说明:
老规矩,先上项目结构(M -> Modify):
└── origin
├── config
│ ├── DataSourceConfig.kt
M │ ├── ServiceAConfig.kt
M │ ├── ServiceBConfig.kt
+ │ └── VendorPropertiesConfig.kt
+ ├── global
+ │ └── JpaProperties.kt
├── serviceA
│ └── dao
│ └── ServiceADao.kt
└── serviceB
└── dao
└── ServiceBDao.kt
// VendorPropertiesConfig.kt
@Configuration
class VendorPropertiesConfig {
/**
*
* @return {JpaProperties} jpaProperties
* 这个类可以覆盖通用属性
*/
@Bean
@ConfigurationProperties(prefix = "spring.jpa.properties.serviceA") // 地址可以随意点,只要不和框架的地址冲突就好
fun getServiceAProperties(): JpaProperties {
return JpaProperties() // 这里用自己写的JpaProperties类,注意不要导错包
}
/**
*
* @return {JpaProperties} jpaProperties
* ServiceB的属性
*/
@Bean
@ConfigurationProperties(prefix = "spring.jpa.properties.serviceB")
fun getServiceBProperties(): JpaProperties {
return JpaProperties()
}
}
JpaProperties我需要说明一下,这里我只列举了几个我用到的配置项,所以只写了四个,你需要以此类推的去写自己用到的选项。
这里的格式我参考了Druid的写法。
// JpaProperties.kt
/**
*
* 说明一下,这就是个kotlin版的JavaBean,你只需要把它作为JavaBean写,然后加上两个内部的处理方法就好了,该写getter/setter的写getter/setter。
* 写lombok的嘛...我不建议写lombok,本龙是亲身体验过lombok版本问题导致的项目无法运行,别问我为什么不改版本,因为EAP和Ultimate的lombok版本本来就不同步。
*/
data class JpaProperties(
var ddl_auto: String?,
var dialect: String?,
var physical_naming_strategy: String?,
var implicit_naming_strategy: String?,
) {
constructor() : this(null, null, null, null)
private fun setConfig(): HashMap<String, *> {
val properties = HashMap<String, Any>()
val prefix = "hibernate."
if (!ddl_auto.isNullOrBlank())
properties["${prefix}ddl-auto"] = ddl_auto!!
if (!dialect.isNullOrBlank())
properties["${prefix}dialect"] = dialect!!
if (!physical_naming_strategy.isNullOrBlank())
properties["${prefix}physical_naming_strategy"] = physical_naming_strategy!!
if (!implicit_naming_strategy.isNullOrBlank())
properties["${prefix}implicit_naming_strategy"] = implicit_naming_strategy!!
return properties
}
fun getProperties(): HashMap<String, *> {
return setConfig()
}
}
// DataSourceConfig.kt
@Configuration
class DataSourceConfig {
@Primary // 默认数据库要加Primary关键词修饰
@Bean("serviceADataSource") // Bean名称,还是起一下的好
@Qualifier("serviceADataSource") // 数据源的分类标记(就像公狗在树下撒尿)
// yml or properties下的配置内容,将内容通过控制中心直接注入
@ConfigurationProperties(prefix = "spring.datasource.serviceA")
fun serviceADataSource(): DataSource {
return DruidDataSource()
// 这里,如果用原生的数据库的话,用下面注释掉的内容即可(我在下面的properties配置中仅配置了Druid的写法,原生的需要你自己去改写)
// return DataSourceBuilder.create().build()
}
@Bean("serviceBDataSource")
@Qualifier("serviceBDataSource")
@ConfigurationProperties(prefix = "spring.datasource.serviceB")
fun serviceBDataSource(): DataSource {
return DruidDataSource()
}
}
// ServiceAConfig.kt
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "serviceAEntityManagerFactory",
transactionManagerRef = "serviceATransactionManager",
basePackages = ["com.arunoido.origin.serviceA"] // 这里是数据库指向的包名,我这里用的是我自己的包名。愿意的话你可以具体指向到自己的Dao层([com.arunoido.origin.serviceA.dao])
)
class ServiceAConfig {
/*todo Modify*/@Autowired
/*todo Modify*/lateinit var vendorPropertiesConfig: VendorPropertiesConfig
@Autowired
@Qualifier("serviceADataSource")
private lateinit var dataSource: DataSource
@Primary
@Bean(name = ["serviceAEntityManager"])
fun entityManager(builder: EntityManagerFactoryBuilder): EntityManager? {
return entityManagerFactory(builder).getObject()?.createEntityManager()
}
@Primary
@Bean(name = ["serviceAEntityManagerFactory"])
fun entityManagerFactory(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
return builder
.dataSource(dataSource)
/*todo Modify*/.properties(vendorPropertiesConfig.getServiceAProperties().getProperties())
.packages("com.arunoido.origin.serviceA.model") // 设置实体类所在位置
.build()
}
@Primary
@Bean(name = ["serviceATransactionManager"])
fun transactionManager(builder: EntityManagerFactoryBuilder): PlatformTransactionManager? {
return JpaTransactionManager(entityManagerFactory(builder).getObject()!!)
}
}
// ServiceBConfig.kt
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "serviceBEntityManagerFactory",
transactionManagerRef = "serviceBTransactionManager",
basePackages = ["com.arunoido.origin.serviceB"] // 这里是数据库指向的包名,我这里用的是我自己的包名。愿意的话你可以具体指向到自己的Dao层([com.arunoido.origin.serviceB.dao])
)
class ServiceBConfig {
/*todo Modify*/@Autowired
/*todo Modify*/lateinit var vendorPropertiesConfig: VendorPropertiesConfig
@Autowired
@Qualifier("serviceBDataSource")
private lateinit var dataSource: DataSource
@Bean(name = ["serviceBEntityManager"])
fun entityManager(builder: EntityManagerFactoryBuilder): EntityManager? {
return entityManagerFactory(builder).getObject()?.createEntityManager()
}
@Bean(name = ["serviceBEntityManagerFactory"])
fun entityManagerFactory(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
return builder
.dataSource(dataSource)
/*todo Modify*/.properties(vendorPropertiesConfig.getServiceBProperties().getProperties())
.packages("com.arunoido.origin.serviceB.model") // 设置实体类所在位置
.build()
}
@Bean(name = ["serviceBTransactionManager"])
fun transactionManager(builder: EntityManagerFactoryBuilder): PlatformTransactionManager? {
return JpaTransactionManager(entityManagerFactory(builder).getObject()!!)
}
}
配置文件:
└── resources
M ├── application-dev.properties
└── application.properties
properties
# todo serviceA添加内容
spring.jpa.properties.serviceA.ddl_auto=update
spring.jpa.properties.serviceA.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# todo serviceB添加内容
spring.jpa.properties.serviceB.ddl_auto=update
spring.jpa.properties.serviceB.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# todo 如果需要的话,serviceCDE随你添加,前面只要按照模式添加即可
# ServiceA的数据库
spring.datasource.serviceA.url=jdbc:mysql://ipaddress:port/serviceA?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.serviceA.username=username
spring.datasource.serviceA.password=pwd
spring.datasource.serviceA.driver-class-name=com.mysql.cj.jdbc.Driver
# ServiceB的数据库
spring.datasource.serviceB.url=jdbc:mysql://ipaddress:port/serviceB?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.serviceB.username=username
spring.datasource.serviceB.password=pwd
spring.datasource.serviceB.driver-class-name=com.mysql.cj.jdbc.Driver
# 通用的JPA配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# Druid的配置,如果不用Druid的话自己配置一下
# 初始化大小,最小,最大
spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=10
#配置获取连接等待超时的时间
spring.datasource.druid.max-wait=60000
#打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=1000
spring.datasource.druid.filter.stat.merge-sql=false
spring.datasource.druid.filter.wall.config.multi-statement-allow=true
因为写博客的时候是十一点半,现在已经是早上六点,四点钟的样子感觉整条龙都飘了一下下...总之,如果有问题务必联系(我不想丢一篇错误的博客误导人,目前为止我自己测试是没问题)
这两天项目整合到另一个服务器,然后会再写一篇Java和Mybatis的多数据库配置,我想那个人用的会多一些的吧...
其实写完这篇文章的时候已经是2号了,整个博客期间我将代码封装了一次,然后调试,测试就用了差不多五个小时才算是得到这样的结果。当时这个项目项目在配置数据库的时候就用了一天半的时间,很多东西都是新接触的,并不能理解,只是在自己配置完了回头看的时候才是清晰的。
然后这次的项目自己尝试用了一次kotlin,写得十分费力。主要问题还是在不能new对象。。。真的是,把我们我这种单身人..啊不,龙士最享受的事情剥削了。 SpringBoot我倒是没有系统学过,完全凭对其机制的理解瞎摸,基本上试两次就能成。
下一个项目计划开始用RPC开始敲了,一点点进步吧,很快后台的所有类型的业务都要摸完一遍了,算法和人工智能的学习也不能怠惰,还有那些罄竹难书未实现的疯狂的梦想。
争取23之前把所有该学的东西都学完...还有三年,时间不多了。
...说起来——最近好像新法律公布蹲幼儿园要介入刑事责任了???
SpringBoot2项目中(JPA + Druid)使用多数据源
原文:https://www.cnblogs.com/Arunoido/p/14221519.html