Spring Boot 3 + JPA多模块系统对MySQL和DORIS进行多数据源集成实战(荣耀典藏版)

前言

大家好,我是月夜枫,没错这个不是凑数的。

背景

  • 最近公司要求系统内使用DORIS做数据分析,同时需要保留原来MySQL部分,因此需要从配置上对多数据源进行集成。

主模块配置

java 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/database
    username: username
    password: password
    type: com.alibaba.druid.pool.DruidDataSource
    druid: # https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
      # 连接池的配置信息
      # 初始化大小,最小,最大
      initial-size: 2
      min-idle: 2
      # 最大连接数建议 (CPU核核数 * 2 + 1)
      max-active: 5
      validation-query: SELECT 1 FROM DUAL
      validation-query-timeout: 1
      # 设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过manEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
      test-while-idle: true
      # 指明是否在从池中取出连接时进行检查,每次都检查, validation-query 不能为空
      test-on-borrow: false
      # 指明是否在归还到池中前进行检查
      test-on-return: false
      # 打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn
      keep-alive: true
      # 打开PSCache,Oracle等支持游标的数据库,打开此开关,会以数量级提升性能,具体查阅PSCache相关资料
      pool-prepared-statements: true
      # 指定每个连接上PSCache的大小
      max-pool-prepared-statement-per-connection-size: 20
      #filters: stat,wall,slf4j #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,slf4j
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: '/druid/*'
        exclusions: '/druid/*,*.html,*.htm,*.js,*.css,*.gif,*.jpg,*.bmp,*.png,*.ico,*.svg,*.ttf,*.woff,*.woff2'
      # 配置 DruidStatViewServlet
      stat-view-servlet:
        enabled: true
        url-pattern: '/druid/*'
        # 禁用HTML页面上的"Reset All"功能
        reset-enable: false
        # 登录名
        login-username: druid
        # 登录密码
        login-password: druid
      filter:
        # 数据库监控统计: StatFilter
        stat:
          # 记录慢 sql 配置
          enabled: true
          db-type: mysql
          log-slow-sql: true
          merge-sql: true
          # 慢 sql 标准
          slow-sql-millis: 5000
        # 防火墙,防 sql 注入
        wall:
          db-type: mysql
        slf4j:
          enabled: true
          data-source-log-enabled: false
          connection-close-after-log-enabled: false
          # 格式化 SQL:com.alibaba.druid.filter.logging.LogFilter.logExecutableSql
          statement-executable-sql-log-enable: true
          statement-close-after-log-enabled: false
          result-set-log-enabled: false
  jpa: # jpa + hibernate 配置
    open-in-view: false
    show-sql: true
    database-platform: org.hibernate.dialect.MySQLDialect
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # 数据库与实体映射命名策略
    properties:
      'hibernate.cache.use_second_level_cache': false # 禁用二级缓存
      hibernate:
        show_sql: true
        format_sql: true
        use_sql_comments: true
  • 以上yml配置是放在主模块下的,所以格式并没有做变化,没有多加一层mysql:的层级,将其当成主数据源使用。

  • 在这里使用了druid做数据库连接池,这是原来项目就配置好的,考虑到项目需要sql监控,再加上项目本身数据访问量还是不高的,因此暂缓数据库连接池的替换。

  • JPA的配置上,需要关注的点是这里采用的Hibernate版本是Hibernate 6,MySQL的方言采用MySQLDialect,不同的Hibernate版本需要关注他的方言变化。

  • 另外要注意的是ddl-auto的变化:

java 复制代码
if (StringUtils.hasText(ddlAuto) && !"none".equals(ddlAuto)) {
    result.put("hibernate.hbm2ddl.auto", ddlAuto);
} else {
    result.remove("hibernate.hbm2ddl.auto");
}
  • 在org.springframework.boot.autoconfigure.orm.jpa包下的HibernateProperties中,determineHibernateProperties方法内有上述逻辑判断,当ddl-auto设置为none时,Hibernate不会再对表结构做处理。
java 复制代码
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.annotation.Resource;
import jakarta.persistence.EntityManager;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;

@Configuration
@EnableJpaRepositories(basePackages = "com.mysqlPackages.**.dao.jpa", entityManagerFactoryRef = "mySQLEntityManagerFactory", transactionManagerRef = "mySQLTransactionManager")
public class MySQLConfiguration {

    @Resource
    private JpaProperties jpaProperties;
    @Resource
    private HibernateProperties hibernateProperties;

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties mySQLDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSource mySQLDataSource() {
        return mySQLDataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean mySQLEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
                this.jpaProperties.getProperties(), new HibernateSettings()
        );
        return builder
                .dataSource(mySQLDataSource())
                .packages("com.mysqlPackages.**.entity", "com.core.**")//可配置多个扫描路径,包括Entity和Converter等类的扫描
                .properties(properties)
                .persistenceUnit("mySQLPersistenceUnit")
                .build();
    }

    @Bean
    @Primary
    public PlatformTransactionManager mySQLTransactionManager(EntityManagerFactoryBuilder builder) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(mySQLEntityManagerFactory(builder).getObject());
        return transactionManager;
    }

    @Bean
    public EntityManager mySQLEntityManager(EntityManagerFactoryBuilder builder) {
        return SharedEntityManagerCreator.createSharedEntityManager(Objects.requireNonNull(mySQLEntityManagerFactory(builder).getObject()));
    }

    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManagerFactoryBuilder builder) {
        return new JPAQueryFactory(mySQLEntityManager(builder));
    }
}
  • 配置类中,首先注意通过EnableJpaRepositories配置扫描dao包路径,要与DORIS的路径做区分,避免同时加载到容器中。

  • 添加了注解@Primary标注作为最高优先级的bean,该注解是Spring Boot 3引入的。

  • 引入了DataSourceProperties,在源码上它默认获取的是spring.datasource下的配置,因此实际@ConfigurationProperties注解是不需要进行标注的,这里是为了提供代码可读性依旧标注在配置bean上:

java 复制代码
@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    // 内容
}
  • 同样的,在org.springframework.boot.autoconfigure.orm.jpa包下,JPA的配置引入也是有源码支持的:
java 复制代码
@ConfigurationProperties(
    prefix = "spring.jpa"
)
public class JpaProperties {
    // 内容
}
  • EntityManagerFactory的properties将JPA和Hibernate的配置通过HibernateProperties的determineHibernateProperties方法注入到Map中。

  • 由之前提到的HibernateProperties源码可知,假设需要在配置类中的properties内单独加入ddl-auto的配置,在目前的版本就不能使用spring.jpa.hibernate.ddl-auto而应该使用hibernate.hbm2ddl.auto。

DORIS模块配置

java 复制代码
spring:
  datasource:
    doris:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://ip:port/database
      username: username
      password: password
      type: com.alibaba.druid.pool.DruidDataSource
      druid: # https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
        # 连接池的配置信息
        # 初始化大小,最小,最大
        initial-size: 2
        min-idle: 2
        # 最大连接数建议 (CPU核核数 * 2 + 1)
        max-active: 5
        validation-query: SELECT 1 FROM DUAL
        validation-query-timeout: 1
        # 设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过manEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
        test-while-idle: true
        # 指明是否在从池中取出连接时进行检查,每次都检查, validation-query 不能为空
        test-on-borrow: false
        # 指明是否在归还到池中前进行检查
        test-on-return: false
        # 打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn
        keep-alive: true
        # 打开PSCache,Oracle等支持游标的数据库,打开此开关,会以数量级提升性能,具体查阅PSCache相关资料
        pool-prepared-statements: true
        # 指定每个连接上PSCache的大小
        max-pool-prepared-statement-per-connection-size: 20
        #filters: stat,wall,slf4j #配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,slf4j
        # 配置DruidStatFilter
        web-stat-filter:
          enabled: true
          url-pattern: '/druid/*'
          exclusions: '/druid/*,*.html,*.htm,*.js,*.css,*.gif,*.jpg,*.bmp,*.png,*.ico,*.svg,*.ttf,*.woff,*.woff2'
        # 配置 DruidStatViewServlet
        stat-view-servlet:
          enabled: true
          url-pattern: '/druid/*'
          # 禁用HTML页面上的"Reset All"功能
          reset-enable: false
          # 登录名
          login-username: druid
          # 登录密码
          login-password: druid
        filter:
          # 数据库监控统计: StatFilter
          stat:
            # 记录慢 sql 配置
            enabled: true
            db-type: mysql
            log-slow-sql: true
            merge-sql: true
            # 慢 sql 标准
            slow-sql-millis: 5000
          # 防火墙,防 sql 注入
          wall:
            db-type: mysql
          slf4j:
            enabled: true
            data-source-log-enabled: false
            connection-close-after-log-enabled: false
            # 格式化 SQL:com.alibaba.druid.filter.logging.LogFilter.logExecutableSql
            statement-executable-sql-log-enable: true
            statement-close-after-log-enabled: false
            result-set-log-enabled: false
  • DORIS的yml配置大体没什么变化,主要是增加了doris:层级用于与主数据源区分。
java 复制代码
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.annotation.Resource;
import jakarta.persistence.EntityManager;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Objects;

@Configuration
@EnableJpaRepositories(basePackages = "com.dorisPackadges.**.dao.jpa",
        entityManagerFactoryRef = "dorisEntityManagerFactory",
        transactionManagerRef = "dorisTransactionManager")
public class DorisConfiguration {

    @Resource
    private JpaProperties jpaProperties;
    @Resource
    private HibernateProperties hibernateProperties;

    @Bean
    @ConfigurationProperties("spring.datasource.doris")
    public DataSourceProperties dorisDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.doris")
    public DataSource dorisDataSource() {
        return dorisDataSourceProperties().initializeDataSourceBuilder().build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean dorisEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
                this.jpaProperties.getProperties(), new HibernateSettings()
        );
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = builder
                .dataSource(dorisDataSource())
                .properties(properties)
                .packages("com.dorisPackadges.entity.doris", "com.core.**")//可配置多个扫描路径,包括Entity和Converter等类的扫描
                .persistenceUnit("dorisPersistenceUnit")
                .build();
        return entityManagerFactoryBean;
    }

    @Bean
    public PlatformTransactionManager dorisTransactionManager(EntityManagerFactoryBuilder builder) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(dorisEntityManagerFactory(builder).getObject());
        return transactionManager;
    }

    @Bean
    public EntityManager dorisEntityManager(EntityManagerFactoryBuilder builder) {
        return SharedEntityManagerCreator.createSharedEntityManager(Objects.requireNonNull(dorisEntityManagerFactory(builder).getObject()));
    }

    @Bean
    public JPAQueryFactory dorisJpaQueryFactory(EntityManagerFactoryBuilder builder) {
        return new JPAQueryFactory(dorisEntityManager(builder));
    }
}
  • DORIS的配置类变化也不是很大,主要有以下几点:

  • 扫描包路径更改为DORIS对应路径;

  • DataSourceProperties需要指定配置@ConfigurationProperties("spring.datasource.doris");

  • JPA的配置可共用,与MySQL差别不大。

其他配置

  • 由于使用到了多模块的配置,除了不同环境的yml配置文件集成外,不同模块的配置文件也需要集成,因此我的application.yml设计如下:
java 复制代码
spring:
  profiles: # 动态环境
    active: dev # "@env@" # 采坑记录,使用 @@ 获取 pom.xml 环境变量时,必须指定 build > resources > resource > <filtering>true</filtering>
    include: doris
  • include实际指向的是DORIS模块下的application-doris.yml配置文件。
java 复制代码
@Configuration
public class JpaConfiguration {

    public static JPAQueryFactory getJpaQueryFactory() {
        return BeanUtils.getBean("jpaQueryFactory");
    }
}
  • 如果用到了QueryDSL,那么不同数据源对应的不同的JpaQueryFactory需要做不同的引用,这里就只能用beanName在容器中做bean的指定了。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

我从清晨走过,也拥抱夜晚的星辰,人生没有捷径,你我皆平凡,你好,陌生人,一起共勉。

相关推荐
武子康1 小时前
Java-01 深入浅出 MyBatis 入门与核心原理:半自动 ORM 框架详解
java·后端·mybatis
木易 士心1 小时前
Java 跳出多层循环
java·开发语言·后端
神奇小汤圆1 小时前
背了那么久的慢 SQL 八股,不如动手跑一遍 EXPLAIN
后端
ClouGence1 小时前
我们做了个疯狂的决定,把 CloudDM 全部开源了
数据库·后端·mysql
努力努力再努力wz1 小时前
【Qt入门系列】深入理解信号与槽:从事件响应到自定义信号机制
c语言·开发语言·数据结构·数据库·c++·qt·mysql
神奇小汤圆2 小时前
MySQL慢查询优化案例:真实案例+EXPLAIN分析——性能提升10倍!
后端
Java成神之路-2 小时前
解密 MySQL 索引性能:为什么主键必须有序?
mysql
北风朝向2 小时前
Spring Boot 集成 Open WebUI 实现 AI 流式对话
人工智能·spring boot·状态模式
未若君雅裁2 小时前
MySQL索引原理-InnoDB-B+树结构与查询过程
b树·mysql