From 9bf7ff23c02f68e492f377a0781eb607e7515440 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 26 May 2020 14:19:47 +0200 Subject: [PATCH] Introduce EntityManager initialization callbacks JpaVendorAdapter.postProcessEntityManager and EntityManagerFactoryInfo.createNativeEntityManager SPI, plus convenient setEntityManagerInitializer configuration options. Closes gh-25125 --- .../HibernateTransactionManager.java | 20 ++++++- .../jpa/AbstractEntityManagerFactoryBean.java | 60 +++++++++++++++++-- .../orm/jpa/EntityManagerFactoryInfo.java | 29 ++++++--- .../orm/jpa/ExtendedEntityManagerCreator.java | 4 +- .../orm/jpa/JpaTransactionManager.java | 44 +++++++++++--- .../orm/jpa/JpaVendorAdapter.java | 12 +++- .../jpa/vendor/AbstractJpaVendorAdapter.java | 6 +- .../support/PersistenceInjectionTests.java | 13 ++-- 8 files changed, 155 insertions(+), 33 deletions(-) diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java index da96519b48..b7619fad9b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -18,6 +18,7 @@ package org.springframework.orm.hibernate5; import java.sql.Connection; import java.sql.ResultSet; +import java.util.function.Consumer; import javax.persistence.PersistenceException; import javax.sql.DataSource; @@ -125,6 +126,9 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana private boolean hibernateManagedSession = false; + @Nullable + private Consumer sessionInitializer; + @Nullable private Object entityInterceptor; @@ -300,6 +304,17 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana this.hibernateManagedSession = hibernateManagedSession; } + /** + * Specify a callback for customizing every Hibernate {@code Session} resource + * created for a new transaction managed by this {@code HibernateTransactionManager}. + *

This enables convenient customizations for application purposes, e.g. + * setting Hibernate filters. + * @since 5.3 + */ + public void setSessionInitializer(Consumer sessionInitializer) { + this.sessionInitializer = sessionInitializer; + } + /** * Set the bean name of a Hibernate entity interceptor that allows to inspect * and change property values before writing to and reading from the database. @@ -462,6 +477,9 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana Session newSession = (entityInterceptor != null ? obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() : obtainSessionFactory().openSession()); + if (this.sessionInitializer != null) { + this.sessionInitializer.accept(session); + } if (logger.isDebugEnabled()) { logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction"); } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java index 84b42e3747..b5d521bc89 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -32,6 +32,7 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.function.Consumer; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; @@ -115,6 +116,9 @@ public abstract class AbstractEntityManagerFactoryBean implements @Nullable private JpaVendorAdapter jpaVendorAdapter; + @Nullable + private Consumer entityManagerInitializer; + @Nullable private AsyncTaskExecutor bootstrapExecutor; @@ -193,8 +197,8 @@ public abstract class AbstractEntityManagerFactoryBean implements * {@code Persistence.createEntityManagerFactory} (if any). *

Can be populated with a String "value" (parsed via PropertiesEditor) or a * "props" element in XML bean definitions. - * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map) - * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map) + * @see javax.persistence.Persistence#createEntityManagerFactory(String, Map) + * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map) */ public void setJpaProperties(Properties jpaProperties) { CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.jpaPropertyMap); @@ -204,8 +208,8 @@ public abstract class AbstractEntityManagerFactoryBean implements * Specify JPA properties as a Map, to be passed into * {@code Persistence.createEntityManagerFactory} (if any). *

Can be populated with a "map" or "props" element in XML bean definitions. - * @see javax.persistence.Persistence#createEntityManagerFactory(String, java.util.Map) - * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(javax.persistence.spi.PersistenceUnitInfo, java.util.Map) + * @see javax.persistence.Persistence#createEntityManagerFactory(String, Map) + * @see javax.persistence.spi.PersistenceProvider#createContainerEntityManagerFactory(PersistenceUnitInfo, Map) */ public void setJpaPropertyMap(@Nullable Map jpaProperties) { if (jpaProperties != null) { @@ -290,6 +294,20 @@ public abstract class AbstractEntityManagerFactoryBean implements return this.jpaVendorAdapter; } + /** + * Specify a callback for customizing every {@code EntityManager} created + * by the exposed {@code EntityManagerFactory}. + *

This is an alternative to a {@code JpaVendorAdapter}-level + * {@code postProcessEntityManager} implementation, enabling convenient + * customizations for application purposes, e.g. setting Hibernate filters. + * @since 5.3 + * @see JpaVendorAdapter#postProcessEntityManager + * @see JpaTransactionManager#setEntityManagerInitializer + */ + public void setEntityManagerInitializer(Consumer entityManagerInitializer) { + this.entityManagerInitializer = entityManagerInitializer; + } + /** * Specify an asynchronous executor for background bootstrapping, * e.g. a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}. @@ -472,6 +490,7 @@ public abstract class AbstractEntityManagerFactoryBean implements EntityManager rawEntityManager = (args.length > 1 ? getNativeEntityManagerFactory().createEntityManager((Map) args[1]) : getNativeEntityManagerFactory().createEntityManager()); + postProcessEntityManager(rawEntityManager); return ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, true); } @@ -498,6 +517,7 @@ public abstract class AbstractEntityManagerFactoryBean implements if (retVal instanceof EntityManager) { // Any other createEntityManager variant - expecting non-synchronized semantics EntityManager rawEntityManager = (EntityManager) retVal; + postProcessEntityManager(rawEntityManager); retVal = ExtendedEntityManagerCreator.createApplicationManagedEntityManager(rawEntityManager, this, false); } return retVal; @@ -555,6 +575,36 @@ public abstract class AbstractEntityManagerFactoryBean implements } } + @Override + public EntityManager createNativeEntityManager(@Nullable Map properties) { + EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? + getNativeEntityManagerFactory().createEntityManager(properties) : + getNativeEntityManagerFactory().createEntityManager()); + postProcessEntityManager(rawEntityManager); + return rawEntityManager; + } + + /** + * Optional callback for post-processing the native EntityManager + * before active use. + *

The default implementation delegates to + * {@link JpaVendorAdapter#postProcessEntityManager}, if available. + * @param rawEntityManager the EntityManager to post-process + * @since 5.3 + * @see #createNativeEntityManager + * @see JpaVendorAdapter#postProcessEntityManager + */ + protected void postProcessEntityManager(EntityManager rawEntityManager) { + JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter(); + if (jpaVendorAdapter != null) { + jpaVendorAdapter.postProcessEntityManager(rawEntityManager); + } + Consumer customizer = this.entityManagerInitializer; + if (customizer != null) { + customizer.accept(rawEntityManager); + } + } + @Override @Nullable public PersistenceUnitInfo getPersistenceUnitInfo() { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java index 0f83f3d9c5..2b4d72159b 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2020 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. @@ -16,6 +16,8 @@ package org.springframework.orm.jpa; +import java.util.Map; + import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.spi.PersistenceProvider; @@ -36,12 +38,6 @@ import org.springframework.lang.Nullable; */ public interface EntityManagerFactoryInfo { - /** - * Return the raw underlying EntityManagerFactory. - * @return the unadorned EntityManagerFactory (never {@code null}) - */ - EntityManagerFactory getNativeEntityManagerFactory(); - /** * Return the underlying PersistenceProvider that the underlying * EntityManagerFactory was created with. @@ -105,4 +101,23 @@ public interface EntityManagerFactoryInfo { */ ClassLoader getBeanClassLoader(); + /** + * Return the raw underlying EntityManagerFactory. + * @return the unadorned EntityManagerFactory (never {@code null}) + */ + EntityManagerFactory getNativeEntityManagerFactory(); + + /** + * Create a native JPA EntityManager to be used as the framework-managed + * resource behind an application-level EntityManager handle. + *

This exposes a native {@code EntityManager} from the underlying + * {@link #getNativeEntityManagerFactory() native EntityManagerFactory}, + * taking {@link JpaVendorAdapter#postProcessEntityManager(EntityManager)} + * into account. + * @since 5.3 + * @see #getNativeEntityManagerFactory() + * @see EntityManagerFactory#createEntityManager() + */ + EntityManager createNativeEntityManager(@Nullable Map properties); + } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java index 00364fb0f4..bfdc08711a 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java @@ -172,9 +172,7 @@ public abstract class ExtendedEntityManagerCreator { Assert.notNull(emf, "EntityManagerFactory must not be null"); if (emf instanceof EntityManagerFactoryInfo) { EntityManagerFactoryInfo emfInfo = (EntityManagerFactoryInfo) emf; - EntityManagerFactory nativeEmf = emfInfo.getNativeEntityManagerFactory(); - EntityManager rawEntityManager = (!CollectionUtils.isEmpty(properties) ? - nativeEmf.createEntityManager(properties) : nativeEmf.createEntityManager()); + EntityManager rawEntityManager = emfInfo.createNativeEntityManager(properties); return createProxy(rawEntityManager, emfInfo, true, synchronizedWithTransaction); } else { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index baca6c773e..2746339bc6 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -19,6 +19,7 @@ package org.springframework.orm.jpa; import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.function.Consumer; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; @@ -128,6 +129,9 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager private JpaDialect jpaDialect = new DefaultJpaDialect(); + @Nullable + private Consumer entityManagerInitializer; + /** * Create a new JpaTransactionManager instance. @@ -298,6 +302,21 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager return this.jpaDialect; } + /** + * Specify a callback for customizing every {@code EntityManager} resource + * created for a new transaction managed by this {@code JpaTransactionManager}. + *

This is an alternative to a factory-level {@code EntityManager} customizer + * and to a {@code JpaVendorAdapter}-level {@code postProcessEntityManager} + * callback, enabling specific customizations of transactional resources. + * @since 5.3 + * @see #createEntityManagerForTransaction() + * @see AbstractEntityManagerFactoryBean#setEntityManagerInitializer + * @see JpaVendorAdapter#postProcessEntityManager + */ + public void setEntityManagerInitializer(Consumer entityManagerInitializer) { + this.entityManagerInitializer = entityManagerInitializer; + } + /** * Retrieves an EntityManagerFactory by persistence unit name, if none set explicitly. * Falls back to a default EntityManagerFactory bean if no persistence unit specified. @@ -398,7 +417,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager EntityManager em = txObject.getEntityManagerHolder().getEntityManager(); // Delegate to JpaDialect for actual transaction begin. - final int timeoutToUse = determineTimeout(definition); + int timeoutToUse = determineTimeout(definition); Object transactionData = getJpaDialect().beginTransaction(em, new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder())); txObject.setTransactionData(transactionData); @@ -452,18 +471,27 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager /** * Create a JPA EntityManager to be used for a transaction. *

The default implementation checks whether the EntityManagerFactory - * is a Spring proxy and unwraps it first. + * is a Spring proxy and delegates to + * {@link EntityManagerFactoryInfo#createNativeEntityManager} + * if possible which in turns applies + * {@link JpaVendorAdapter#postProcessEntityManager(EntityManager)}. * @see javax.persistence.EntityManagerFactory#createEntityManager() - * @see EntityManagerFactoryInfo#getNativeEntityManagerFactory() */ protected EntityManager createEntityManagerForTransaction() { EntityManagerFactory emf = obtainEntityManagerFactory(); - if (emf instanceof EntityManagerFactoryInfo) { - emf = ((EntityManagerFactoryInfo) emf).getNativeEntityManagerFactory(); - } Map properties = getJpaPropertyMap(); - return (!CollectionUtils.isEmpty(properties) ? - emf.createEntityManager(properties) : emf.createEntityManager()); + EntityManager em; + if (emf instanceof EntityManagerFactoryInfo) { + em = ((EntityManagerFactoryInfo) emf).createNativeEntityManager(properties); + } + else { + em = (!CollectionUtils.isEmpty(properties) ? + emf.createEntityManager(properties) : emf.createEntityManager()); + } + if (this.entityManagerInitializer != null) { + this.entityManagerInitializer.accept(em); + } + return em; } /** diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java index c8fc8ca968..4d283283a3 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 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. @@ -137,4 +137,14 @@ public interface JpaVendorAdapter { default void postProcessEntityManagerFactory(EntityManagerFactory emf) { } + /** + * Optional callback for post-processing the native EntityManager + * before active use. + *

This can be used for setting vendor-specific parameters, e.g. + * Hibernate filters, on every new EntityManager. + * @since 5.3 + */ + default void postProcessEntityManager(EntityManager em) { + } + } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/AbstractJpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/AbstractJpaVendorAdapter.java index 8d07ee6c60..37203ab9c4 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/AbstractJpaVendorAdapter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/AbstractJpaVendorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -160,4 +160,8 @@ public abstract class AbstractJpaVendorAdapter implements JpaVendorAdapter { public void postProcessEntityManagerFactory(EntityManagerFactory emf) { } + @Override + public void postProcessEntityManager(EntityManager em) { + } + } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java index 3114573ed0..61d04f1a94 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceInjectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -105,7 +105,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT } @Test - public void testPublicExtendedPersistenceContextSetter() throws Exception { + public void testPublicExtendedPersistenceContextSetter() { EntityManager mockEm = mock(EntityManager.class); given(mockEmf.createEntityManager()).willReturn(mockEm); @@ -123,7 +123,7 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT } @Test - public void testPublicSpecificExtendedPersistenceContextSetter() throws Exception { + public void testPublicSpecificExtendedPersistenceContextSetter() { EntityManagerFactory mockEmf2 = mock(EntityManagerFactory.class); EntityManager mockEm2 = mock(EntityManager.class); given(mockEmf2.createEntityManager()).willReturn(mockEm2); @@ -194,16 +194,15 @@ public class PersistenceInjectionTests extends AbstractEntityManagerFactoryBeanT } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void testPublicExtendedPersistenceContextSetterWithEntityManagerInfoAndSerialization() throws Exception { EntityManager mockEm = mock(EntityManager.class, withSettings().serializable()); given(mockEm.isOpen()).willReturn(true); EntityManagerFactoryWithInfo mockEmf = mock(EntityManagerFactoryWithInfo.class); - given(mockEmf.getNativeEntityManagerFactory()).willReturn(mockEmf); given(mockEmf.getJpaDialect()).willReturn(new DefaultJpaDialect()); - given(mockEmf.getEntityManagerInterface()).willReturn((Class)EntityManager.class); + given(mockEmf.getEntityManagerInterface()).willReturn((Class) EntityManager.class); given(mockEmf.getBeanClassLoader()).willReturn(getClass().getClassLoader()); - given(mockEmf.createEntityManager()).willReturn(mockEm); + given(mockEmf.createNativeEntityManager(null)).willReturn(mockEm); GenericApplicationContext gac = new GenericApplicationContext(); gac.getDefaultListableBeanFactory().registerSingleton("entityManagerFactory", mockEmf);