Merge pull request #14928 from nicce

* pr/14928:
  Polish "Enhance multiple entity manager factories how-to"
  Enhance multiple entity manager factories how-to

Closes gh-14928
This commit is contained in:
Stephane Nicoll 2021-04-19 17:01:08 +02:00
commit 57c199d4d1
3 changed files with 84 additions and 36 deletions

View File

@ -79,6 +79,7 @@ dependencies {
implementation("org.assertj:assertj-core")
implementation("org.glassfish.jersey.core:jersey-server")
implementation("org.hibernate:hibernate-jcache")
implementation("org.springframework:spring-orm")
implementation("org.springframework:spring-test")
implementation("org.springframework:spring-web")
implementation("org.springframework:spring-webflux")

View File

@ -1799,45 +1799,27 @@ Spring Boot auto-configuration switches off its entity manager in the presence o
[[howto-use-two-entity-managers]]
=== Use Two EntityManagers
Even if the default `EntityManagerFactory` works fine, you need to define a new one, otherwise the presence of the second bean of that type switches off the default.
You can use the `EntityManagerBuilder` provided by Spring Boot to help you to create one.
Alternatively, you can use the `LocalContainerEntityManagerFactoryBean` directly from Spring ORM, as shown in the following example:
[[howto-use-multiple-entity-managers]]
=== Using Multiple EntityManagerFactories
If you need to use JPA against multiple data sources, you likely need one `EntityManagerFactory` per data source.
The `LocalContainerEntityManagerFactoryBean` from Spring ORM allows you to configure an `EntityManagerFactory` for your needs.
You can also reuse `JpaProperties` to bind settings for each `EntityManagerFactory`, as shown in the following example:
[source,java,indent=0,subs="verbatim,quotes,attributes"]
----
// add two data sources configured as above
@Bean
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(customerDataSource())
.packages(Customer.class)
.persistenceUnit("customers")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean orderEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(orderDataSource())
.packages(Order.class)
.persistenceUnit("orders")
.build();
}
include::{code-examples}/jpa/CustomEntityManagerFactoryExample.java[tag=configuration]
----
The example above creates an `EntityManagerFactory` using a `DataSource` bean named `firstDataSource`.
It scans entities located in the same package as `Order`.
It is possible to map additional JPA properties using the `app.first.jpa` namespace.
NOTE: When you create a bean for `LocalContainerEntityManagerFactoryBean` yourself, any customization that was applied during the creation of the auto-configured `LocalContainerEntityManagerFactoryBean` is lost.
For example, in case of Hibernate, any properties under the `spring.jpa.hibernate` prefix will not be automatically applied to your `LocalContainerEntityManagerFactoryBean`.
If you were relying on these properties for configuring things like the naming strategy or the DDL mode, you will need to explicitly configure that when creating the `LocalContainerEntityManagerFactoryBean` bean.
On the other hand, properties that get applied to the auto-configured `EntityManagerFactoryBuilder`, which are specified via `spring.jpa.properties`, will automatically be applied, provided you use the auto-configured `EntityManagerFactoryBuilder` to build the `LocalContainerEntityManagerFactoryBean` bean.
The configuration above almost works on its own.
To complete the picture, you need to configure `TransactionManagers` for the two `EntityManagers` as well.
If you mark one of them as `@Primary`, it could be picked up by the default `JpaTransactionManager` in Spring Boot.
The other would have to be explicitly injected into a new instance.
You should provide a similar configuration for any additional data sources for which you need JPA access.
To complete the picture, you need to configure a `JpaTransactionManager` for each `EntityManagerFactory` as well.
Alternatively, you might be able to use a JTA transaction manager that spans both.
If you use Spring Data, you need to configure `@EnableJpaRepositories` accordingly, as shown in the following example:
@ -1845,16 +1827,16 @@ If you use Spring Data, you need to configure `@EnableJpaRepositories` according
[source,java,indent=0,subs="verbatim,quotes,attributes"]
----
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Customer.class,
entityManagerFactoryRef = "customerEntityManagerFactory")
public class CustomerConfiguration {
@EnableJpaRepositories(basePackageClasses = Order.class,
entityManagerFactoryRef = "firstEntityManagerFactory")
public class OrderConfiguration {
...
}
@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Order.class,
entityManagerFactoryRef = "orderEntityManagerFactory")
public class OrderConfiguration {
@EnableJpaRepositories(basePackageClasses = Customer.class,
entityManagerFactoryRef = "secondEntityManagerFactory")
public class CustomerConfiguration {
...
}
----

View File

@ -0,0 +1,65 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.docs.jpa;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
/**
* Example configuration for a custom JPA entity manager.
*
* @author Stephane Nicoll
*/
public class CustomEntityManagerFactoryExample {
// tag::configuration[]
@Bean
@ConfigurationProperties("app.jpa.first")
public JpaProperties firstJpaProperties() {
return new JpaProperties();
}
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource firstDataSource,
JpaProperties firstJpaProperties) {
EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(firstJpaProperties);
return builder.dataSource(firstDataSource).packages(Order.class).persistenceUnit("firstDs").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties.getProperties(), null);
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
// Map JPA properties as needed
return new HibernateJpaVendorAdapter();
}
// end::configuration[]
private static class Order {
}
}