diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 3220e7e5c7..337893939a 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -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") diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc index 25395c1f5e..2ded7e6bba 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc @@ -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 { ... } ---- diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/jpa/CustomEntityManagerFactoryExample.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/jpa/CustomEntityManagerFactoryExample.java new file mode 100644 index 0000000000..a7a1ee5d11 --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/jpa/CustomEntityManagerFactoryExample.java @@ -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 { + + } + +}