ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot2项目中(JPA + Druid)使用多数据源

2021-01-02 06:32:06  阅读:284  来源: 互联网

标签:return JPA 数据源 kt druid Druid datasource spring properties


SpringBoot项目中JPA使用多数据源(举例用Database和Druid两种配置方式 注:我仅写Druid的基础数据库配置)

注:代码部分因为影响阅读我将它们折叠起来惹,注意前面有小箭头的文本嗷

本文代码篇幅较长,我愿意写,你愿意听看嘛?

技术栈(仅说一些必要的,记着要对症下药,避免因为环境不对而不能使用)

  1. mysql-connector-java 8.0.22 // 说真的有了druid之后我有段怀疑有没有必要要这个东西了... 等看明白了druid之后我回来再更新以下。
  2. druid-spring-boot-starter 1.2.3
  3. spring-boot 2.4
  4. spring-boot-starter-data-jpa

P.S.

需要的jar包可以直接在MavenRepository里搜索下载

这里的配置是基于注册中心的

业务背景只是些杂谈,具体实现直接跳转到实现过程

预先说明

本项目内容我是使用Kotlin编写的,如果你用的IDE是IJ的产品,那么可以直接复制到Java代码中,IDE会自动编译成Java代码,但是不能保证所有的代码都是正确的,所以需要自己手动修复一部分,放心,不会太多的。

喵的用了三天时间才完成这个模块...kotlin害龙不浅呐(小声bb)

对于这部分代码我计划加上其它功能后封装一下,作为一个模版项目开源,不过短时间里并没有足够的时间去做它。

业务背景

在写了对方三个管理系统之后,展开了一次新的关于数据整合的业务,在这个业务中,我们需要拿到多个项目后台的数据集。 在这里我想到了两种解决方案,分别介绍一下其优劣。

以下内容我将跑业务的服务器统称为业务后台,将整合数据使用的后台称为数据后台

  1. 通过不同业务中的后台中提供相对应的api来获取所需要的数据
    • ★ 可以更快地实现(添加接口)而无需重新配置一个项目(懒人专用)
    • ★ 对于数据后台来说能够更好的管理接口(通用的东西很多,可以很好地实现模块化)
    • ☆ 权限的对接要单独写一个模块
    • ☆ 如果图表有更变的话,需要修改所对应的业务,这样会让项目变得很乱
    • ☆ 除了查询的网络请求延时之外,中间还会再加一段网络数据请求(几乎可以无视,除非——)
  2. 一个后台进行多个数据库的链接,自己拉取得所需要的数据
    • ★ 修改时不容易影响到其它的业务(独立服务)
    • ★ 减少中间的数据请求过程,让工厂与卖家少一层代理(你们都知道代理是要赚钱的吧?)

    • 数据整合统一在一个地方,易于处理,方便中间的数据测试(不需要再改大量的配置文件,不过我确定现在有办法解决这个问题,貌似阿里的学习套件里就包含了test和prod的运行环境部署,或者是部署为docker镜像,不过我还没尝试过,暂时)

既然是做了数据的整合,对于多数据库的访问就是必不可少的了,接下来的就是这篇文章的正题。

实现过程

SpringBoot配置数据库有两个阶段(2、3):

  1. 配置文件中加入数据信息(注册中心的方式配置)
  2. DataSourceConfig(入口 注入一些基本信息,类似于对象生成)
  3. DataBaseConfig(数据库配置 目的在于指定数据库所服务的区域)

在这里,分为两个步骤实现,第一步实现通用方法,第二步是实现分库配置的方法

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
// 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
// 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
// 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.

这里我使用的配置文件是跑在开发环境的properties,如果你习惯写yml的话可以自己改过去,关键词相同,只是结构不同了(我其实挺喜欢yml的结构的,主要是想尝试下新东西,嗯)

然后使用dev的配置是在application.properties中的spring.profiles.active=dev

application-dev.properties
# 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=create-drop
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

通用方法覆写(定制化配置)

说明:

一般情况下,我们需要用两种jpa的策略的时候才会用到这里的内容,否则上面的默认配置完全足够使用。

我举一个最简单的可以用到这种方式配置的场景——业务A的数据库使用的是Mysql,业务B使用的数据库是Oracle,这个时候就需要把他们的Driver分别配置了。

老规矩,先上项目结构(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
// 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
// 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
// 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()
    }
}
M ServiceAConfig.kt
// 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
// 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

JpaProperties.kt
# todo serviceA添加内容
spring.jpa.properties.serviceA.ddl_auto=create
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=create-drop
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

总结(我对总结的定义是:如果将这个内容出成一道考题的话,那么这里的内容是应该是可以直接解答问题的)

  • 项目配置文件(.properties or .yml)需要添加上两个数据库的基本信息,两个信息需要能够区分开且不能与原生的配置字段冲突
  • DataSourceConfig.kt 数据库配置的入口文件,在这里声明DataSourceBuilder()
  • ServiceA,B,C,D进行多个数据源的分发,将数据源分发到对应需要的包下
  • 如果通用的配置无法满足,可以用新的配置覆盖掉某个源的配置,需要用到VendorPropertiesConfig.kt,同时准备一个JavaBean处理注册中心注入的数据
  • enjoy coding

    标签:return,JPA,数据源,kt,druid,Druid,datasource,spring,properties
    来源: https://www.cnblogs.com/Arunoido/p/14221519.html

    本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
    2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
    3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
    4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
    5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有