springboot+jpa多数据源配置实例

springboot+jpa配置多数据源一直都在听说,没有实际动手演练,今天动手一试,发现有一些麻烦,麻烦的地方在于,需要严格区分多种数据源带来的变化,实体需要区分,dao层需要区分,service一般来说是事务控制的入口,既然底层数据来源都不同,service层也是需要严格区分的,所以说controller,service,dao三层架构的系统来说,就需要改变service,dao相关的数据库配置。只有controller可以勉强避免修改。

这里所说的多数据源可以是同一个类型的数据库的两个实例,也可以是不同数据库的两个实例,这里以mysql和postgresql两个数据库为例,来介绍如何做多数据源配置。以及多数据源配置可能带来的问题。

构建maven工程的时候,除了springboot基础依赖,就是spring-boot-starter-data-jpa,以及mysql,postgresql驱动。

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.4.RELEASE</version>
  <relativePath/>
</parent>

<dependencies>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-configuration-processor</artifactId>
	<optional>true</optional>
</dependency>
</dependencies>
<build>
  <plugins>
	 <plugin>
		 <groupId>org.springframework.boot</groupId>
		 <artifactId>spring-boot-maven-plugin</artifactId>
		 <configuration>
			<fork>true</fork>
			<addResources>true</addResources>
		 </configuration>
	 </plugin>
  </plugins>
</build>

我们在前面说了从service到dao,再到实体类,既然数据库来源不同,他们在创建的时候就要考虑做区分。使用jpa做数据持久化,我们需要实体管理器EntityManager,而EntityManager需要实体管理器工厂类EntityManagerFactory,而实体管理器工厂类EntityManagerFactory需要数据源DataSource,另外要做事务管理,需要JpaTransactionManager,它也需要数据源,整个配置还是我们在做xml配置的思路一样。 

这里我们将mysql配置作为默认数据源primaryDataSource,另外一个数据源我们称之为otherDataSource,使用properties配置文件,这样配置更容易理解,占用篇幅也少,application.properties内容如下:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8&useUnicode=true
spring.datasource.username=root
spring.datasource.password=root
#other
spring.datasource.other.driver-class-name=org.postgresql.Driver
spring.datasource.other.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.other.username=postgres
spring.datasource.other.password=
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.open-in-view=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.max_fetch_depth=1
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.hbm2ddl=update
#spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
spring.jackson.serialization.fail-on-empty-beans=false

 数据源配置类:

package com.xxx.springboot.config;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class DataSourceConfig {
	@Bean(name="dataSource")
	@Primary
	@ConfigurationProperties(prefix="spring.datasource")
	public DataSource dataSource(){
		//return DataSourceBuilder.create().build();
		return dataSourceProperties().initializeDataSourceBuilder().build();
	}
	
	@Primary
	@Bean(name="dataSourceProperties")
	@ConfigurationProperties(prefix="spring.datasource")
	public DataSourceProperties dataSourceProperties(){
		return new DataSourceProperties();
	}
	
	
	@Bean(name="otherDataSource")
	@ConfigurationProperties(prefix="spring.datasource.other")
	public DataSource otherDataSource(){
		//return DataSourceBuilder.create().build();
		return otherDataSourceProperties().initializeDataSourceBuilder().build();
	}
	
	
	@Bean(name="otherDataSourceProperties")
	@ConfigurationProperties(prefix="spring.datasource.other")
	public DataSourceProperties otherDataSourceProperties(){
		return new DataSourceProperties();
	}
	
}

因为application.properties中指定数据源url使用的属性是url,而不是jdbc-url,因此,这里的数据源配置类,我们没有使用默认的方式创建:

DataSourceBuilder.create().build()

而使用的是:

new DataSourceProperties().initializeDataSourceBuilder().build()

如果不是这么来做,会报错:jdbcUrl is required with driverClassName

我们还需要配置各自的EntityManager,PlatformTransactionManager:

mysql数据源对应的相关配置:

package com.xxx.springboot.config;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		entityManagerFactoryRef="entityManagerFactoryPrimary",
		transactionManagerRef="transactionManagerPrimary",
		basePackages={"com.xxx.springboot.dao.mysql"})
public class PrimaryDataSourceConfig {
	@Autowired
	@Qualifier("dataSource")
	private DataSource dataSource;
	
	@Autowired
	private JpaProperties jpaProperties;
	
	@Primary
	@Bean(name="entityManagerPrimary")
	public EntityManager entityManager(EntityManagerFactoryBuilder builder){
		return entityManagerFactoryBean(builder).getObject().createEntityManager();
	}
	
	@Primary
	@Bean(name="entityManagerFactoryPrimary")
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder){
		return builder
				.dataSource(dataSource)
				.properties(getProperties())
				.packages("com.xxx.springboot.domain.mysql")
				.persistenceUnit("primaryPersistentUnit")
				.build();
	}
	
	public Map<String, String> getProperties(){
		Map<String, String> map = new HashMap<String, String>();
		map.put("format_sql", "true");
		map.put("max_fetch_depth", "1");
		return map;
	}
	
	
	@Primary
	@Bean(name="transactionManagerPrimary")
	public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder){
		return new JpaTransactionManager(entityManagerFactoryBean(builder).getObject());
	}
}

postgresql数据源对应的相关配置: 

package com.xxx.springboot.config;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		entityManagerFactoryRef="entityManagerFactoryOther",
		transactionManagerRef="transactionManagerOther",
	    basePackages={"com.xxx.springboot.dao.postgresql"})
public class OtherDataSourceConfig {

	@Autowired
	@Qualifier("otherDataSource")
	private DataSource otherDataSource;
	
	@Autowired
	private JpaProperties jpaProperties;
	
	@Bean(name="entityManagerOther")
	public EntityManager entityManager(EntityManagerFactoryBuilder builder){
		return entityManagerFactoryBean(builder).getObject().createEntityManager();
	}
	
	@Bean(name="entityManagerFactoryOther")
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder){
		return builder
				.dataSource(otherDataSource)
				.properties(getProperties())
				.packages("com.xxx.springboot.domain.postgresql")
				.persistenceUnit("otherPersistentUnit")
				.build();
	}
	
	public Map<String, String> getProperties(){
		Map<String, String> map = new HashMap<String, String>();
		map.put("format_sql", "true");
		map.put("max_fetch_depth", "1");
		//map.put("dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
		return map;
	}
	
	@Bean(name="transactionManagerOther")
	public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder){
		return new JpaTransactionManager(entityManagerFactoryBean(builder).getObject());
	}
}

两个配置类,除了注解不一样之外,代码几乎一样,这就是多数据源配置显得有意思的地方,其实一点也不冗余,否则,就无法彻底分离两个数据源。配置类中,通过配置属性prefix前缀来获取对应的数据源信息。另外,这里有个主配置,默认我们使用的是mysql数据库,因此在mysql相关的配置如:DataSource,EntityManager,EntityManagerFactory,TransactionManager配置上除了@Bean(name="")和@Qualifer()来指定他们的区别之外,还有一个@Primary注解,表示主配置。

 配置中我们还通过basePackages属性指定了Repository的位置,也就是dao层接口的位置,还通过packages属性指定了各自实体类的位置。这样,数据源配置清楚了,剩下就是各自数据源对应的实体和dao,service编码了。

dao层很简单,就是一个接口,然后继承JpaRepository<Entity,ID>就可以了。service层就是调用dao层的方法。为了解决hibernate懒加载的问题,我们需要在service层接口上注入@Transaction注解。如果不加注解,可以在配置文件中加入配置:

#spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

关于懒加载的问题,还想再说一下,报错一般是这样的:LazyInitializationException:could not initialize proxy [com.xxx....] - no session,原因是,我们在通过dao.getOne()这样的请求,他是返回一个代理,并不是直接查询的数据库,当真正需要使用数据的时候,才会去数据库查询,而这时候,session已经关闭。这个问题很奇怪,如果我们使用的是dao.findById(id)却不会遇到这样的懒加载异常问题,因为它直接取查了数据库,所以底层原因是懒加载导致的。解决问题的办法有以下几种方案:

1、取消懒加载,这个在实体中通过注解@Proxy(lazy=false)来实现。

2、既然session已经关闭,那么我们让session在使用期间一直开启。这就回到了openSessionInView的解决办法了,这种办法在单元测试service方法的时候却不生效,不知道为什么。无论我们配置spring.jpa.open-in-view=true还是在启动类中增加如下代码:

@Bean
public OpenEntityManagerInViewFilter openEntityManagerInViewFilter(){
	return new OpenEntityManagerInViewFilter();
}

有人说通过这种办法能够解决,但是对于dao.getOne(id)这个方法来说是不生效的。 

3、 这里涉及到两个事务,为了解决这个问题,可以让调用getOne(id)的方法在一个事务中,因此我们可以在调用get(id)->getOne(id)的方法体上加上事务@Transaction,也可以解决问题,尤其是在单元测试的时候,我们可以这么来做:

@Test
@Transactional
public void query(){
	User user  = userService.get(14);
	System.out.println(user);
}

但是这毕竟是单元测试,实际中,调用service层方法的是controller层,我们不可能将事务加载controller层上面。实际上,在真正启动项目进行测试的时候发现,即使controller层不加事务注解,我们访问controller对应的接口,也是没有任何问题的,不会出现懒加载异常,这个一直是我不太理解的地方。

 4、这个是一个终极解决办法,就是统一配置hibernate的属性,让系统所有的请求不执行懒加载,而不用在每个实体上通过@Proxy(lazy=false)来注解解决。这个属性就是spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true。在多数据源配置的时候,这个配置我们配置在默认配置文件中application.properties中,只会对主数据源生效,而其他数据源不会生效,我们需要在对数据源进行单独配置。这就是我们数据源配置的部分:

@Bean(name="entityManagerFactoryOther")
public LocalContainerEntityManagerFactoryBean 
entityManagerFactoryBean(EntityManagerFactoryBuilder builder){
	return builder
			.dataSource(otherDataSource)
			.properties(getProperties())
			.packages("com.xxx.springboot.domain.postgresql")
			.persistenceUnit("otherPersistentUnit")
			.build();
}

public Map<String, String> getProperties(){
	Map<String, String> map = new HashMap<String, String>();
	map.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect");
	map.put("hibernate.enable_lazy_load_no_trans", "true");
	return map;
}

 

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot中配置JPA多数据源的方法如下: 1. 首先,在pom.xml文件中添加必要的依赖项,包括Spring Boot、JPA和数据库驱动程序。 2. 创建对应的数据源配置类,包括数据库连接信息、用户名、密码等。可以创建多个数据源配置类,每个数据源对应一个数据库。 3. 创建对应的JdbcTemplate实例,用于在代码中执行SQL语句。每个数据源都需要创建一个对应的JdbcTemplate对象。 4. 创建指定数据源的方法,用于在代码中动态切换数据源。可以使用@Primary注解指定默认的数据源。 5. 创建Jpa事务管理器,用于管理多个数据源的事务。每个数据源都需要创建一个对应的Jpa事务管理器。 6. 使用AOP的切面来切换数据源。可以使用@Before和@After注解来分别在方法执行前和执行后进行数据源的切换和恢复。 以上是配置JPA多数据源的一般步骤,根据具体需求和情况,可能还需要进行一些额外的配置和调整。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [SpringBoot-JPA多数据源](https://blog.csdn.net/zZZ251682553/article/details/125105388)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [springboot jpa 多数据源配置](https://blog.csdn.net/chenxing1990/article/details/122808525)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luffy5459

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值