Introduce EntityManager initialization callbacks

JpaVendorAdapter.postProcessEntityManager and EntityManagerFactoryInfo.createNativeEntityManager SPI, plus convenient setEntityManagerInitializer configuration options.

Closes gh-25125
This commit is contained in:
Juergen Hoeller 2020-05-26 14:19:47 +02:00
parent a853a58c62
commit 9bf7ff23c0
8 changed files with 155 additions and 33 deletions

View File

@ -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<Session> 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}.
* <p>This enables convenient customizations for application purposes, e.g.
* setting Hibernate filters.
* @since 5.3
*/
public void setSessionInitializer(Consumer<Session> 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");
}

View File

@ -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<EntityManager> entityManagerInitializer;
@Nullable
private AsyncTaskExecutor bootstrapExecutor;
@ -193,8 +197,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
* {@code Persistence.createEntityManagerFactory} (if any).
* <p>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).
* <p>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<String, ?> 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}.
* <p>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<EntityManager> 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.
* <p>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<EntityManager> customizer = this.entityManagerInitializer;
if (customizer != null) {
customizer.accept(rawEntityManager);
}
}
@Override
@Nullable
public PersistenceUnitInfo getPersistenceUnitInfo() {

View File

@ -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.
* <p>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);
}

View File

@ -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 {

View File

@ -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<EntityManager> 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}.
* <p>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<EntityManager> 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.
* <p>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<String, Object> 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;
}
/**

View File

@ -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.
* <p>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) {
}
}

View File

@ -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) {
}
}

View File

@ -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);