## 简介
在下是刚毕业的小萌新,现在在一家股票资讯公司做Java开发,手头上的项目(crud项目)本来是只连Mysql,现新增了功能需要连Postgre,于是哼哧哼哧开始了JPA的多数据源配置。经历一番轰轰烈烈的搜索,找了好几个多数据源的配置教程,就开始了我的模仿表演。没想到一步一个坑,经过几周的摸索,总算是爬了出来。吾日三省吾身,于是决定记下这坑爹的经历。
## 多数据配置
开门见山,先贴出配置代码,之后再说说里面的坑
1. **项目结构**
common:一些工具类、通用dto
dao:数据访问层,查询方法
domain:实体
service:业务实现
web:接口定义
2. **application-dev.properties**
```
# datasource配置
# 主数据源
spring.datasource.druid.primary.url=jdbc:mysql://xxxx:3306/xxxx?characterEncoding=utf8&useSSL=true&serverTimezone=UTC
spring.datasource.druid.primary.username=root
spring.datasource.druid.primary.password=root
spring.datasource.druid.primary.driver-class-name=com.mysql.jdbc.Driver
# 第二数据源
spring.datasource.druid.secondary.url=jdbc:mysql://xxxx:5432/postgres?useUnicode=true&characterEncoding=utf8¤tSchema=db40
spring.datasource.druid.secondary.username=root
spring.datasource.druid.secondary.password=root
spring.datasource.druid.secondary.driver-class-name=com.postgresql.Driver
# 监控
spring.datasource.druid.use-globall-data-source-stat=true
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.allow=
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=admin
spring.datasource.druid.stat-view-servlet.reset-enable=false
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
# 主数据源连接池配置
spring.datasource.druid.primary.initial-size=2
spring.datasource.druid.primary.max-active=30
spring.datasource.druid.primary.min-idle=2
spring.datasource.druid.primary.max-wait=60000
spring.datasource.druid.primary.validation-query=select 'a'
spring.datasource.druid.primary.test-on-borrow=false
spring.datasource.druid.primary.test-on-return=false
spring.datasource.druid.primary.test-while-idle=true
spring.datasource.druid.primary.time-between-eviction-runs-millis=60000
spring.datasource.druid.primary.min-evictable-idle-time-millis=300000
spring.datasource.druid.primary.filters=stat
spring.datasource.druid.primary.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.primary.WebStatFilter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.primary.pool-prepared-statements=true
spring.datasource.druid.primary.max-pool-prepared-statement-per-connection-size=20
# 第二数据源连接池配置
spring.datasource.druid.secondary.initial-size=2
spring.datasource.druid.secondary.max-active=30
spring.datasource.druid.secondary.min-idle=2
spring.datasource.druid.secondary.max-wait=60000
spring.datasource.druid.secondary.validation-query=select 'a'
spring.datasource.druid.secondary.test-on-borrow=false
spring.datasource.druid.secondary.test-on-return=false
spring.datasource.druid.secondary.test-while-idle=true
spring.datasource.druid.secondary.time-between-eviction-runs-millis=60000
spring.datasource.druid.secondary.min-evictable-idle-time-millis=300000
spring.datasource.druid.secondary.filters=stat
spring.datasource.druid.secondary.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.secondary.useGlobalDataSourceStat=true
spring.datasource.druid.secondary.WebStatFilter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
spring.datasource.druid.secondary.pool-prepared-statements=true
spring.datasource.druid.secondary.max-pool-prepared-statement-per-connection-size=20
# JPA配置
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.fomat_sql=true
spring.jpa.open-in-view=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.primary-dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.secondary-dialect=org.hibernate.dialect.PostgreSQL9Dialect
```
3. **DruidDataSourceConfig.java**
```
# Druid配置
@Configuration
public class DruidDataSourceConfig {
@Primary
@Qualifier("primaryDataSource")
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Qualifier("secondaryDataSource")
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
```
4. **PrimaryConfig.java**
```
# 主数据源配置-mysql
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.myhexin.graph.dao.mysql.*" }) //设置Repository所在位置
public class PrimaryConfig {
@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;
@Autowired
private JpaProperties jpaPreperties;
@Value("${spring.jpa.hibernate.primary-dialect}")
private String primaryDialect;
@Primary
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("com.myhexin.graph.domain.mysql.*") //设置实体类所在位置
.persistenceUnit("primaryPersistenceUnit")
.build();
}
private Map<String, String> getVendorProperties(DataSource dataSource) {
# 手动设置命名策略(可选)
Map<String,String> map=new HashMap<>(2);
map.put("hibernate.dialect",primaryDialect);
jpaProperties.setProperies(map);
return jpaProperties.getHibernateProperties(dataSource);
}
@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}
}
```
5. **SecondaryConfig.java**
```
# 第二数据源配置-pg
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactorySecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages= { "com.myhexin.graph.dao.pg.*" }) //设置Repository所在位置
public class SecondaryConfig {
@Autowired
@Qualifier("secondaryDataSource")
private DataSource secondaryDataSource;
@Autowired
private JpaProperties jpaPreperties;
@Value("${spring.jpa.hibernate.secondary-dialect}")
private String secondaryDialect;
@Bean(name = "entityManagerSecondary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
}
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(secondaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("com.myhexin.graph.domain.pg.*") //设置实体类所在位置
.persistenceUnit("secondaryPersistenceUnit")
.build();
}
private Map<String, String> getVendorProperties(DataSource dataSource) {
# 手动设置命名策略(可选)
Map<String,String> map=new HashMap<>(2);
map.put("hibernate.dialect",secondaryDialect);
jpaProperties.setProperies(map);
jpaProperties.getHibernate().getNaming().setPhysicalStrategy("org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl");
return jpaProperties.getHibernateProperties(dataSource);
}
@Bean(name = "transactionManagerSecondary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}
}
```
## 需要注意的点(坑)
1. **与mysql的连接到达8小时自动断开**
本人由于没有手动配置Druid(DruidDataSourceConfig.java中没有使用DruidDataSourceBuilder),导致使用了自带的jdbc连接池。
MySQL默认的“wait_timeout”是28800秒即8小时,意味着如果一个连接的空闲时间超过8个小时,MySQL将自动断开该连接,而jdbc连接池却认为该连接还是有效的(因为并未校验连接的有效性),当应用申请使用该连接时,就会报连接超时。
这里我们使用Druid连接池就可以解决问题,毕竟线上的数据库配置是不能更改的。
2. **连接间歇性断开,EntityManager实体刷新错误**
在解决了问题1之后,本以为大功告成,没想到仍然出现了连接断开的问题。
刚开始怀疑连接池中连接断开,于是进入Druid查看了连接,发现连接没有问题。
经过一番调试,发现只有特定的几个函数会报出连接断开和实体刷新失败的异常,其他crud操作没有影响。
查了很多资料,后来同事说,EntityManager的注入方式可能有问题。
于是乎我去service层看了EntityManager的注入,使用的是@Autowired,应该使用@PersistenceContext注入。
总结:EntityManager需要使用@PersistenceContext注入,否则会出现实体刷新失败、连接断开等诡异问题。
```
# 之前
@Autowired
private EntityManager em;
# 之后,注意unitName参数为entityManagerFactory
@PersistenceContext(unitName="entityManagerFactorySecondary")
private EntityManager em;
```
3. **实体的字段名无法映射到数据库(pg)**
问题3现象:使用@Column(name="XXX")指定映射字段无效,数据库里会出现你的变量名字段
这种情况很迷,我虽然是解决了,但是没想通其中的原因。
第一:指定命名策略,由于pg与mysql字段命名策略不同,所以在配置数据源配置时,手动设置了命名策略,见secondaryConfig.java
第二:@Column注解打在getter方法上,这个没能想到原因,但这样操作确实解决了问题。
```
# 之前
@Column(name="F001N_AI005")
private Long logicGraphVseq;
# 之后
private Long logicGraphVseq;
@Column(name="F001N_AI005")
public Long getLogicGraphVseq(){
return logicGraphVseq;
}
```
## 体会
照着别人的配置,没想到出了这么多问题,也没有找到一个完全可用的教程,所以这里记录下了自己的配置和问题,代码在内网,这些都是手打的,如果有拼写错误请自行更正,若在配置的时候遇到问题,可以留言,在下只是一个刚毕业的小萌新,写的不对的地方还请指正。
## 参考
1. JPA @PersistenceContext注解问题
https://www.debugease.com/javaweb/193936.html
2. springBoot+Hibernate(Jpa)多数据源配置与使用
https://blog.csdn.net/qq_41690306/article/details/79304501