## 簡介
在下是剛畢業(yè)的小萌新,現(xiàn)在在一家股票資訊公司做Java開發(fā),手頭上的項目(crud項目)本來是只連Mysql,現(xiàn)新增了功能需要連Postgre,于是哼哧哼哧開始了JPA的多數(shù)據(jù)源配置。經(jīng)歷一番轟轟烈烈的搜索,找了好幾個多數(shù)據(jù)源的配置教程,就開始了我的模仿表演。沒想到一步一個坑,經(jīng)過幾周的摸索,總算是爬了出來。吾日三省吾身,于是決定記下這坑爹的經(jīng)歷。
## 多數(shù)據(jù)配置
開門見山,先貼出配置代碼,之后再說說里面的坑
1. **項目結(jié)構(gòu)**
common:一些工具類、通用dto
dao:數(shù)據(jù)訪問層,查詢方法
domain:實(shí)體
service:業(yè)務(wù)實(shí)現(xiàn)
web:接口定義
2. **application-dev.properties**
```
# datasource配置
# 主數(shù)據(jù)源
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
# 第二數(shù)據(jù)源
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
# 監(jiān)控
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/*
# 主數(shù)據(jù)源連接池配置
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
# 第二數(shù)據(jù)源連接池配置
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**
```
# 主數(shù)據(jù)源配置-mysql
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
? ? ? ? entityManagerFactoryRef="entityManagerFactoryPrimary",
? ? ? ? transactionManagerRef="transactionManagerPrimary",
? ? ? ? basePackages= { "com.myhexin.graph.dao.mysql.*" }) //設(shè)置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.*") //設(shè)置實(shí)體類所在位置
? ? ? ? ? ? ? ? .persistenceUnit("primaryPersistenceUnit")
? ? ? ? ? ? ? ? .build();
? }
? ? private Map<String, String> getVendorProperties(DataSource dataSource) {
? ? # 手動設(shè)置命名策略(可選)
? ? 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**
```
# 第二數(shù)據(jù)源配置-pg
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
? ? ? ? entityManagerFactoryRef="entityManagerFactorySecondary",
? ? ? ? transactionManagerRef="transactionManagerSecondary",
? ? ? ? basePackages= { "com.myhexin.graph.dao.pg.*" }) //設(shè)置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.*") //設(shè)置實(shí)體類所在位置
? ? ? ? ? ? ? ? .persistenceUnit("secondaryPersistenceUnit")
? ? ? ? ? ? ? ? .build();
? }
? ? private Map<String, String> getVendorProperties(DataSource dataSource) {
? ? # 手動設(shè)置命名策略(可選)
? ? 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());
? ? }
}
```
## 需要注意的點(diǎn)(坑)
1. **與mysql的連接到達(dá)8小時自動斷開**
本人由于沒有手動配置Druid(DruidDataSourceConfig.java中沒有使用DruidDataSourceBuilder),導(dǎo)致使用了自帶的jdbc連接池。
MySQL默認(rèn)的“wait_timeout”是28800秒即8小時,意味著如果一個連接的空閑時間超過8個小時,MySQL將自動斷開該連接,而jdbc連接池卻認(rèn)為該連接還是有效的(因為并未校驗連接的有效性),當(dāng)應(yīng)用申請使用該連接時,就會報連接超時。
這里我們使用Druid連接池就可以解決問題,畢竟線上的數(shù)據(jù)庫配置是不能更改的。
2. **連接間歇性斷開,EntityManager實(shí)體刷新錯誤**
在解決了問題1之后,本以為大功告成,沒想到仍然出現(xiàn)了連接斷開的問題。
剛開始懷疑連接池中連接斷開,于是進(jìn)入Druid查看了連接,發(fā)現(xiàn)連接沒有問題。
經(jīng)過一番調(diào)試,發(fā)現(xiàn)只有特定的幾個函數(shù)會報出連接斷開和實(shí)體刷新失敗的異常,其他crud操作沒有影響。
查了很多資料,后來同事說,EntityManager的注入方式可能有問題。
于是乎我去service層看了EntityManager的注入,使用的是@Autowired,應(yīng)該使用@PersistenceContext注入。
總結(jié):EntityManager需要使用@PersistenceContext注入,否則會出現(xiàn)實(shí)體刷新失敗、連接斷開等詭異問題。
```
# 之前
@Autowired
private EntityManager em;
# 之后,注意unitName參數(shù)為entityManagerFactory
@PersistenceContext(unitName="entityManagerFactorySecondary")
private EntityManager em;
```
3. **實(shí)體的字段名無法映射到數(shù)據(jù)庫(pg)**
問題3現(xiàn)象:使用@Column(name="XXX")指定映射字段無效,數(shù)據(jù)庫里會出現(xiàn)你的變量名字段
這種情況很迷,我雖然是解決了,但是沒想通其中的原因。
第一:指定命名策略,由于pg與mysql字段命名策略不同,所以在配置數(shù)據(jù)源配置時,手動設(shè)置了命名策略,見secondaryConfig.java
第二:@Column注解打在getter方法上,這個沒能想到原因,但這樣操作確實(shí)解決了問題。
```
# 之前
@Column(name="F001N_AI005")
private Long logicGraphVseq;
# 之后
private Long logicGraphVseq;
@Column(name="F001N_AI005")
public Long getLogicGraphVseq(){
return logicGraphVseq;
}
```
## 體會
照著別人的配置,沒想到出了這么多問題,也沒有找到一個完全可用的教程,所以這里記錄下了自己的配置和問題,代碼在內(nèi)網(wǎng),這些都是手打的,如果有拼寫錯誤請自行更正,若在配置的時候遇到問題,可以留言,在下只是一個剛畢業(yè)的小萌新,寫的不對的地方還請指正。
## 參考
1. JPA @PersistenceContext注解問題
https://www.debugease.com/javaweb/193936.html
2. springBoot+Hibernate(Jpa)多數(shù)據(jù)源配置與使用
https://blog.csdn.net/qq_41690306/article/details/79304501