SpringBoot+JPA+Druid多数据源配置完全版+有坑提示

## 简介

在下是刚毕业的小萌新,现在在一家股票资讯公司做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&currentSchema=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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343