前言
大家好,我是月夜枫,没错这个不是凑数的。
背景
- 最近公司要求系统内使用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的指定了。
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
我从清晨走过,也拥抱夜晚的星辰,人生没有捷径,你我皆平凡,你好,陌生人,一起共勉。
