工具篇-Spring boot JPA多数据源

时间:2023-03-09 20:19:02
工具篇-Spring boot JPA多数据源
写这篇博文是因为这个东西坑太多,首先说明下边实现的多数据源不是动态切换的,应该算是静态的。另外,如果对Spring JPA不熟悉的话,可以参见:SpringBoot 中 JPA 的使用

坑一、pom文件

pom中spring boot以及mysql connector的版本一定要注意。

  <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/>
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<springboot.version>1.5.8.RELEASE</springboot.version>
<java.version>1.8</java.version>
<spring.boot.mainclass>xxxx.data.xxxx.Application</spring.boot.mainclass>
</properties> <dependencies>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-aop</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-beans</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-core</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.36</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>1.5.8.RELEASE</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<!--<fork>true</fork>
<mainClass>${spring.boot.mainclass}</mainClass>-->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

坑二、config文件

有DataSourceConfig、PrimaryConfig、SecondaryConfig三个配置文件,路径大概是这样的:

工具篇-Spring boot JPA多数据源

工具篇-Spring boot JPA多数据源

首先DataSourceConfig:

 /**
* Multi datasource profile.
* @date 2019/04/26 10:58
*/ @Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
} @Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}

PrimaryConfig:

 @Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages = {"xxxx.data.xxxx.dao.primary.*"})
public class PrimaryConfig { @Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource; @Autowired(required = false)
private JpaProperties jpaProperties; /**
* EntityManager profile.
*/
@Primary
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
} /**
* EntityManagerFactory profile.
*/
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder) {
return builder.dataSource(primaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("xxxx.data.xxxx.entity.primary.*")
.persistenceUnit("primaryPersistenceUnit")
.build();
} /**
* Jpa profile.
*/
private Map<String, String> getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
} /**
* Transaction profile.
*/
@Primary
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
}
}

SecondaryConfig:

 @Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactorySecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages = {"xxxx.data.xxxx.dao.secondary.*"})
public class SecondaryConfig {
@Autowired
@Qualifier("secondaryDataSource")
private DataSource secondaryDataSource; @Autowired(required = false)
private JpaProperties jpaProperties; /**
* EntityManager profile.
*/
@Bean(name = "entityManagerSecondary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactorySecondary(builder).getObject().createEntityManager();
} /**
* EntityManagerFactory profile.
*/
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(secondaryDataSource)
.properties(getVendorProperties(secondaryDataSource))
.packages("xxxx.data.xxxx.entity.secondary.*")
.persistenceUnit("secondaryPersistenceUnit")
.build();
} /**
* Jpa profile.
*/
private Map<String, String> getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
} /**
* Transaction profile.
*/
@Bean(name = "transactionManagerSecondary")
public PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
} }

上边这些配不好,就会报一些烂七八糟的错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'operationLogAspect': Unsatisfied dependency expressed through field 'jobOperationLogDao'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxxxxDao': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class xxxxxx
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.8.RELEASE.jar:1.5.8.RELEASE]
at yidian.data.bear.Application.main(Application.java:18) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxxxxxDao': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Not a managed type: class ......
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.
... 19 common frames omitted
Caused by: java.lang.IllegalArgumentException: Not a managed type: class

当然,如果你使用Springboot2.x的话,getHibernateProperties方法会报错的,对于这种情况,可以参考:https://blog.csdn.net/MrCoderStack/article/details/88658069,附上上边链接中的一张图片。

工具篇-Spring boot JPA多数据源

坑三、数据库配置文件

application-test.properties中配置了两个数据源,注意.url不好使的话,换成.jdbc.url试试:

 #########################################################
### Primary DataSource -- DataSource 1 configuration ###
#########################################################
spring.datasource.primary.url=jdbc:mysql://ip:3307/数据库名
spring.datasource.primary.username=用户名
spring.datasource.primary.password=密码
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver #########################################################
### Secondary DataSource -- DataSource 2 configuration ##
#########################################################
spring.datasource.secondary.url=jdbc:mysql://ip:3309/数据库名
spring.datasource.secondary.username=用户名
spring.datasource.secondary.password=密码
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver #########################################################
### Java Persistence Api -- Spring jpa configuration ###
#########################################################
# Specify the DBMS
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, update, validate)
spring.jpa.hibernate.ddl-auto=validate
# Naming strategy
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# stripped before adding them to the entity manager
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

坑四、Entity映射无自增Id数据库表

JPA entity映射数据库表时需要一个唯一Id,而有的数据库表没有id怎么办,但是如果存在联合主键的话你可以采取IdClass注解的方式,这样的好处是写dao层代码方便,首先把联合主键封装成一个类,如下所示:

 public class ScPK implements Serializable {
@Column(name = "student_name")
private String studentName;
@Column(name = "course_name")
private String courseName;
private String score; public String getStudentName() {
return studentName;
} public void setStudentName(String studentName) {
this.studentName = studentName;
} public String getCourseName() {
return courseName;
} public void setCourseName(String courseName) {
this.courseName = courseName;
} public String getScore() {
return score;
} public void setScore(String score) {
this.score = score;
}
}

然后是实体类:

 @Entity
@Table(name = "test")
@IdClass(ScPK.class)
public class ScEntity {
@Id
@Column(name = "student_name")
private String studentName;
@Id
@Column(name = "course_name")
private String courseName; private String score; public String getStudentName() {
return studentName;
} public void setStudentName(String studentName) {
this.studentName = studentName;
} public String getCourseName() {
return courseName;
} public void setCourseName(String courseName) {
this.courseName = courseName;
} public String getScore() {
return score;
} public void setScore(String score) {
this.score = score;
}
}

也附上dao层代码吧,这里studentName、courseName与实体类中属性保持一致,与数据库表无关:

 @Repository
public interface ScDao extends JpaRepository<ScEntity, Long> { @Query("select l from ScEntity l where l.studentName = :studentName and l.courseName = :courseName")
ScEntity findAllByStudentNameAndCourseName(@Param("studentName") String studentName, @Param("courseName") String courseName);
}

坑五、SpringBoot项目改多数据源后debug启动不起来

我就纳闷怎么改了多数据源,debug启动服务就起不来了,用postman也访问不了。有位大哥or大姐(https://www.cnblogs.com/fightingting/p/9683811.html)说自己几个月一直启动很慢,我是直接起不来了偶偶,幸亏ta提醒:idea左下角出现一行小字,Method breakpoints may dramatically slow down debugging,启动Springboot项目之前把断点去掉就解决了。

坑刘、Hibernate整合SpringBoot配置多数据源后8小时无操作数据库连接失效

问题:应用8小时不访问数据库,数据库连接自动失效访问失败,报错如下:

工具篇-Spring boot JPA多数据源

解决办法:在上面的properties中增加如下配置:

 spring.datasource.primary.test-while-idle=true
spring.datasource.secondary.test-while-idle=true