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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。