mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Early support for JPA 4.0 EntityAgent (autowiring a shared proxy)
Includes skipping of isOpen check on close where easily possible. Closes gh-36025
This commit is contained in:
+26
@@ -69,6 +69,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* a Spring application context. As of 7.0, it additionally exposes a shared
|
||||
* {@link jakarta.persistence.EntityManager} instance through {@link SmartFactoryBean},
|
||||
* making {@code EntityManager} available for dependency injection as well.
|
||||
* As of 7.0.4, it also exposes a JPA 4.0 {@code EntityAgent} if available.
|
||||
*
|
||||
* <p>Encapsulates the common functionality between the different JPA bootstrap
|
||||
* contracts: standalone as well as container. Note that as of 7.0, the JPA 3.2
|
||||
@@ -99,6 +100,10 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
BeanNameAware, InitializingBean, SmartInitializingSingleton, DisposableBean,
|
||||
EntityManagerFactoryInfo, PersistenceExceptionTranslator, Serializable {
|
||||
|
||||
/** JPA 4.0 EntityAgent class, if available. */
|
||||
private static final @Nullable Class<?> ENTITY_AGENT_CLASS = EntityManagerFactoryUtils.getEntityAgentClass();
|
||||
|
||||
|
||||
/** Logger available to subclasses. */
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -112,6 +117,8 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
|
||||
private @Nullable Class<? extends EntityManager> entityManagerInterface;
|
||||
|
||||
private @Nullable Class<?> entityAgentInterface = ENTITY_AGENT_CLASS;
|
||||
|
||||
private @Nullable JpaDialect jpaDialect;
|
||||
|
||||
private @Nullable JpaVendorAdapter jpaVendorAdapter;
|
||||
@@ -138,6 +145,9 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
/** Exposed client-level shared EntityManager proxy. */
|
||||
private @Nullable EntityManager sharedEntityManager;
|
||||
|
||||
/** Exposed client-level shared EntityAgent proxy. */
|
||||
private @Nullable Object sharedEntityAgent;
|
||||
|
||||
|
||||
/**
|
||||
* Set the PersistenceProvider implementation class to use for creating the
|
||||
@@ -383,6 +393,11 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
this.entityManagerInterface = EntityManager.class;
|
||||
}
|
||||
}
|
||||
if (this.entityAgentInterface != null) {
|
||||
Class<?> vendorInterface = jpaVendorAdapter.getEntityAgentInterface();
|
||||
this.entityAgentInterface = (vendorInterface != null &&
|
||||
ClassUtils.isVisible(vendorInterface, this.beanClassLoader) ? vendorInterface : null);
|
||||
}
|
||||
if (this.jpaDialect == null) {
|
||||
this.jpaDialect = jpaVendorAdapter.getJpaDialect();
|
||||
}
|
||||
@@ -401,7 +416,12 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
// application-managed EntityManager proxy that automatically joins
|
||||
// existing transactions.
|
||||
this.entityManagerFactory = createEntityManagerFactoryProxy(this.nativeEntityManagerFactory);
|
||||
|
||||
this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
|
||||
if (this.entityAgentInterface != null) {
|
||||
this.sharedEntityAgent = SharedEntityManagerCreator.createSharedEntityAgent(
|
||||
this.entityManagerFactory, null, this.entityAgentInterface);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -645,6 +665,9 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
if (EntityManager.class.isAssignableFrom(type)) {
|
||||
return (type.isInstance(this.sharedEntityManager) ? type.cast(this.sharedEntityManager) : null);
|
||||
}
|
||||
if (ENTITY_AGENT_CLASS != null && ENTITY_AGENT_CLASS.isAssignableFrom(type)) {
|
||||
return (type.isInstance(this.sharedEntityAgent) ? type.cast(this.sharedEntityAgent) : null);
|
||||
}
|
||||
return SmartFactoryBean.super.getObject(type);
|
||||
}
|
||||
|
||||
@@ -653,6 +676,9 @@ public abstract class AbstractEntityManagerFactoryBean implements
|
||||
if (EntityManager.class.isAssignableFrom(type)) {
|
||||
return type.isInstance(this.sharedEntityManager);
|
||||
}
|
||||
if (ENTITY_AGENT_CLASS != null && ENTITY_AGENT_CLASS.isAssignableFrom(type)) {
|
||||
return type.isInstance(this.sharedEntityAgent);
|
||||
}
|
||||
return SmartFactoryBean.super.supportsType(type);
|
||||
}
|
||||
|
||||
|
||||
+122
-14
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.orm.jpa;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.persistence.EntityExistsException;
|
||||
@@ -52,7 +53,9 @@ import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.transaction.support.ResourceHolderSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -76,6 +79,12 @@ public abstract class EntityManagerFactoryUtils {
|
||||
public static final int ENTITY_MANAGER_SYNCHRONIZATION_ORDER =
|
||||
DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
|
||||
|
||||
private static final @Nullable Method CREATE_ENTITY_AGENT_METHOD =
|
||||
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent");
|
||||
|
||||
private static final @Nullable Method CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD =
|
||||
ClassUtils.getMethodIfAvailable(EntityManagerFactory.class, "createEntityAgent", Map.class);
|
||||
|
||||
private static final Log logger = LogFactory.getLog(EntityManagerFactoryUtils.class);
|
||||
|
||||
|
||||
@@ -195,9 +204,8 @@ public abstract class EntityManagerFactoryUtils {
|
||||
|
||||
Assert.notNull(emf, "No EntityManagerFactory specified");
|
||||
|
||||
EntityManagerHolder emHolder =
|
||||
(EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
|
||||
if (emHolder != null) {
|
||||
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
|
||||
if (emHolder != null && emHolder.hasEntityManager()) {
|
||||
if (synchronizedWithTransaction) {
|
||||
if (!emHolder.isSynchronizedWithTransaction()) {
|
||||
if (TransactionSynchronizationManager.isActualTransactionActive()) {
|
||||
@@ -232,6 +240,7 @@ public abstract class EntityManagerFactoryUtils {
|
||||
// with no synchronized EntityManager having been requested by application code before.
|
||||
// Unbind in order to register a new unsynchronized EntityManager instead.
|
||||
TransactionSynchronizationManager.unbindResource(emf);
|
||||
emHolder = null;
|
||||
}
|
||||
else {
|
||||
// Either a previously bound unsynchronized EntityManager, or the application
|
||||
@@ -263,21 +272,30 @@ public abstract class EntityManagerFactoryUtils {
|
||||
}
|
||||
|
||||
try {
|
||||
// Use same EntityManager for further JPA operations within the transaction.
|
||||
// Thread-bound object will get removed by synchronization at transaction completion.
|
||||
emHolder = new EntityManagerHolder(em);
|
||||
if (synchronizedWithTransaction) {
|
||||
if (emHolder != null) {
|
||||
emHolder.setEntityManager(em);
|
||||
Object transactionData = prepareTransaction(em, emf);
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true));
|
||||
new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, false));
|
||||
emHolder.setSynchronizedWithTransaction(true);
|
||||
}
|
||||
else {
|
||||
// Unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec...
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionScopedEntityManagerSynchronization(emHolder, emf));
|
||||
// Use same EntityManager for further JPA operations within the transaction.
|
||||
// Thread-bound object will get removed by synchronization at transaction completion.
|
||||
emHolder = new EntityManagerHolder(em);
|
||||
if (synchronizedWithTransaction) {
|
||||
Object transactionData = prepareTransaction(em, emf);
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true));
|
||||
emHolder.setSynchronizedWithTransaction(true);
|
||||
}
|
||||
else {
|
||||
// Unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec...
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionScopedEntityManagerSynchronization(emHolder, emf));
|
||||
}
|
||||
TransactionSynchronizationManager.bindResource(emf, emHolder);
|
||||
}
|
||||
TransactionSynchronizationManager.bindResource(emf, emHolder);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// Unexpected exception from external delegation call -> close EntityManager and rethrow.
|
||||
@@ -288,6 +306,79 @@ public abstract class EntityManagerFactoryUtils {
|
||||
return em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a JPA EntityAgent from the given factory. Binds a corresponding
|
||||
* EntityAgent to the thread when running in a Spring-managed transaction.
|
||||
* @param emf the EntityManagerFactory to create the EntityAgent with
|
||||
* @param properties the properties to be passed into the {@code createEntityAgent}
|
||||
* call (may be {@code null})
|
||||
* @return the EntityAgent, or {@code null} if none found
|
||||
* @throws PersistenceException if the EntityManager couldn't be created
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static @Nullable Object doGetTransactionalEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties)
|
||||
throws PersistenceException {
|
||||
|
||||
Assert.notNull(emf, "No EntityManagerFactory specified");
|
||||
|
||||
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(emf);
|
||||
if (emHolder != null) {
|
||||
if (emHolder.hasEntityAgent()) {
|
||||
return emHolder.getEntityAgent();
|
||||
}
|
||||
}
|
||||
else if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
// Indicate that we can't obtain a transactional EntityManager.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create a new EntityManager for use within the current transaction.
|
||||
logger.debug("Opening JPA EntityAgent");
|
||||
Object entityAgent = createEntityAgent(emf, properties);
|
||||
|
||||
if (emHolder != null) {
|
||||
emHolder.setEntityAgent(entityAgent);
|
||||
}
|
||||
else {
|
||||
emHolder = new EntityManagerHolder(entityAgent);
|
||||
TransactionSynchronizationManager.registerSynchronization(
|
||||
new TransactionScopedEntityManagerSynchronization(emHolder, emf));
|
||||
TransactionSynchronizationManager.bindResource(emf, emHolder);
|
||||
}
|
||||
emHolder.setSynchronizedWithTransaction(true);
|
||||
|
||||
return entityAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new JPA EntityAgent via reflectively detected JPA 4.0 API.
|
||||
* @param emf the EntityManagerFactory to create the EntityAgent with
|
||||
* @param properties the properties to be passed into the {@code createEntityAgent}
|
||||
* call (may be {@code null})
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static Object createEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties) {
|
||||
if (CREATE_ENTITY_AGENT_METHOD == null || CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD == null) {
|
||||
throw new IllegalStateException("JPA 4.0 createEntityAgent API not available");
|
||||
}
|
||||
Object entityAgent = (!CollectionUtils.isEmpty(properties) ?
|
||||
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_WITH_PROPERTIES_METHOD, emf, properties) :
|
||||
ReflectionUtils.invokeMethod(CREATE_ENTITY_AGENT_METHOD, emf));
|
||||
if (entityAgent == null) {
|
||||
throw new IllegalStateException("JPA 4.0 createEntityAgent API returned null");
|
||||
}
|
||||
return entityAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the {@code jakarta.persistence.EntityAgent} class.
|
||||
* @return the {@code EntityAgent} class, or {@code null} if not available
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static @Nullable Class<?> getEntityAgentClass() {
|
||||
return (CREATE_ENTITY_AGENT_METHOD != null ? CREATE_ENTITY_AGENT_METHOD.getReturnType() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a transaction on the given EntityManager, if possible.
|
||||
* @param em the EntityManager to prepare
|
||||
@@ -424,6 +515,23 @@ public abstract class EntityManagerFactoryUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given JPA EntityHandler (EntityManager or EntityAgent),
|
||||
* catching and logging any cleanup exceptions thrown.
|
||||
* @param entityHandler the JPA EntityManager/EntityAgent to close
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static void closeEntityHandler(@Nullable Object entityHandler) {
|
||||
if (entityHandler instanceof AutoCloseable closeable) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("Failed to close JPA EntityHandler", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for resource cleanup at the end of a non-JPA transaction
|
||||
@@ -488,7 +596,7 @@ public abstract class EntityManagerFactoryUtils {
|
||||
|
||||
@Override
|
||||
protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) {
|
||||
closeEntityManager(resourceHolder.getEntityManager());
|
||||
resourceHolder.closeAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -523,7 +631,7 @@ public abstract class EntityManagerFactoryUtils {
|
||||
|
||||
@Override
|
||||
protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) {
|
||||
closeEntityManager(resourceHolder.getEntityManager());
|
||||
resourceHolder.closeAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public class EntityManagerHolder extends ResourceHolderSupport {
|
||||
|
||||
protected @Nullable EntityManager entityManager;
|
||||
|
||||
protected @Nullable Object entityAgent;
|
||||
|
||||
private boolean transactionActive;
|
||||
|
||||
private @Nullable SavepointManager savepointManager;
|
||||
@@ -48,12 +50,37 @@ public class EntityManagerHolder extends ResourceHolderSupport {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
EntityManagerHolder(@Nullable Object entityAgent) {
|
||||
this.entityAgent = entityAgent;
|
||||
}
|
||||
|
||||
|
||||
void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
public EntityManager getEntityManager() {
|
||||
Assert.state(this.entityManager != null, "No EntityManager available");
|
||||
return this.entityManager;
|
||||
}
|
||||
|
||||
void setEntityAgent(Object entityAgent) {
|
||||
this.entityAgent = entityAgent;
|
||||
}
|
||||
|
||||
Object getEntityAgent() {
|
||||
Assert.state(this.entityAgent != null, "No EntityAgent available");
|
||||
return this.entityAgent;
|
||||
}
|
||||
|
||||
boolean hasEntityManager() {
|
||||
return (this.entityManager != null);
|
||||
}
|
||||
|
||||
boolean hasEntityAgent() {
|
||||
return (this.entityAgent != null);
|
||||
}
|
||||
|
||||
protected void setTransactionActive(boolean transactionActive) {
|
||||
this.transactionActive = transactionActive;
|
||||
}
|
||||
@@ -79,7 +106,10 @@ public class EntityManagerHolder extends ResourceHolderSupport {
|
||||
}
|
||||
|
||||
protected void closeAll() {
|
||||
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
|
||||
EntityManagerFactoryUtils.closeEntityHandler(this.entityManager);
|
||||
EntityManagerFactoryUtils.closeEntityHandler(this.entityAgent);
|
||||
this.entityManager = null;
|
||||
this.entityAgent = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -124,6 +124,20 @@ public interface JpaVendorAdapter {
|
||||
return EntityManager.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the vendor-specific JPA 4.0 EntityAgent interface
|
||||
* that this provider's EntityAgents will implement.
|
||||
* <p>If the provider does not offer any EntityAgent extensions,
|
||||
* the adapter should simply return the standard
|
||||
* {@link jakarta.persistence.EntityAgent} class here.
|
||||
* @return the EntityAgent class, or {@code null} if not supported
|
||||
* (on JPA 3.2)
|
||||
* @since 7.0.4
|
||||
*/
|
||||
default @Nullable Class<?> getEntityAgentInterface() {
|
||||
return EntityManagerFactoryUtils.getEntityAgentClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional callback for post-processing the native EntityManagerFactory
|
||||
* before active use.
|
||||
|
||||
+196
-53
@@ -177,31 +177,42 @@ public abstract class SharedEntityManagerCreator {
|
||||
ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transactional EntityAgent proxy for the given EntityManagerFactory.
|
||||
* @param emf the EntityManagerFactory to obtain EntityAgentss from as needed
|
||||
* @param properties the properties to be passed into the
|
||||
* {@code createEntityAgent} call (may be {@code null})
|
||||
* @param ifc the interfaces to be implemented by the EntityAgent
|
||||
* @return a shareable transactional EntityAgent proxy
|
||||
* @since 7.0.4
|
||||
*/
|
||||
static Object createSharedEntityAgent(EntityManagerFactory emf, @Nullable Map<?, ?> properties, Class<?> ifc) {
|
||||
ClassLoader cl = null;
|
||||
if (emf instanceof EntityManagerFactoryInfo emfInfo) {
|
||||
cl = emfInfo.getBeanClassLoader();
|
||||
}
|
||||
return Proxy.newProxyInstance(
|
||||
(cl != null ? cl : SharedEntityManagerCreator.class.getClassLoader()),
|
||||
new Class<?>[] {ifc}, new SharedEntityAgentInvocationHandler(emf, properties));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler that delegates all calls to the current
|
||||
* transactional EntityManager, if any; else, it will fall back
|
||||
* to a newly created EntityManager per operation.
|
||||
* Base class for EntityManager and EntityAgent invocation handlers.
|
||||
* @since 7.0.4
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
|
||||
private abstract static class SharedEntityHandlerInvocationHandler implements InvocationHandler, Serializable {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SharedEntityManagerInvocationHandler.class);
|
||||
protected final EntityManagerFactory targetFactory;
|
||||
|
||||
private final EntityManagerFactory targetFactory;
|
||||
protected final @Nullable Map<?, ?> properties;
|
||||
|
||||
private final @Nullable Map<?, ?> properties;
|
||||
|
||||
private final boolean synchronizedWithTransaction;
|
||||
|
||||
private transient volatile @Nullable ClassLoader proxyClassLoader;
|
||||
|
||||
public SharedEntityManagerInvocationHandler(
|
||||
EntityManagerFactory target, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) {
|
||||
protected transient volatile @Nullable ClassLoader proxyClassLoader;
|
||||
|
||||
public SharedEntityHandlerInvocationHandler(EntityManagerFactory target, @Nullable Map<?, ?> properties) {
|
||||
this.targetFactory = target;
|
||||
this.properties = properties;
|
||||
this.synchronizedWithTransaction = synchronizedWithTransaction;
|
||||
initProxyClassLoader();
|
||||
}
|
||||
|
||||
@@ -214,6 +225,65 @@ public abstract class SharedEntityManagerCreator {
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization, just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
// Initialize transient fields.
|
||||
initProxyClassLoader();
|
||||
}
|
||||
|
||||
protected Object invokeMethod(Method method, @Nullable Object[] args, Object target, boolean newTarget)
|
||||
throws Throwable {
|
||||
|
||||
boolean close = newTarget;
|
||||
try {
|
||||
Object result = method.invoke(target, args);
|
||||
if (result instanceof Query query) {
|
||||
if (newTarget) {
|
||||
Class<?>[] ifcs = cachedQueryInterfaces.computeIfAbsent(query.getClass(), key ->
|
||||
ClassUtils.getAllInterfacesForClass(key, this.proxyClassLoader));
|
||||
result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
|
||||
new DeferredQueryInvocationHandler(query, target));
|
||||
close = false;
|
||||
}
|
||||
else {
|
||||
EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
finally {
|
||||
if (close) {
|
||||
EntityManagerFactoryUtils.closeEntityHandler(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler that delegates all calls to the current
|
||||
* transactional EntityManager, if any; else, it will fall back
|
||||
* to a newly created EntityManager per operation.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class SharedEntityManagerInvocationHandler extends SharedEntityHandlerInvocationHandler
|
||||
implements Serializable {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SharedEntityManagerInvocationHandler.class);
|
||||
|
||||
private final boolean synchronizedWithTransaction;
|
||||
|
||||
public SharedEntityManagerInvocationHandler(
|
||||
EntityManagerFactory target, @Nullable Map<?, ?> properties, boolean synchronizedWithTransaction) {
|
||||
|
||||
super(target, properties);
|
||||
this.synchronizedWithTransaction = synchronizedWithTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
|
||||
// Invocation on EntityManager interface coming in...
|
||||
@@ -303,47 +373,120 @@ public abstract class SharedEntityManagerCreator {
|
||||
}
|
||||
|
||||
// Regular EntityManager operations.
|
||||
boolean isNewEm = false;
|
||||
boolean newTarget = false;
|
||||
if (target == null) {
|
||||
logger.debug("Creating new EntityManager for shared EntityManager invocation");
|
||||
target = (!CollectionUtils.isEmpty(this.properties) ?
|
||||
this.targetFactory.createEntityManager(this.properties) :
|
||||
this.targetFactory.createEntityManager());
|
||||
isNewEm = true;
|
||||
newTarget = true;
|
||||
}
|
||||
|
||||
// Invoke method on current EntityManager.
|
||||
try {
|
||||
Object result = method.invoke(target, args);
|
||||
if (result instanceof Query query) {
|
||||
if (isNewEm) {
|
||||
Class<?>[] ifcs = cachedQueryInterfaces.computeIfAbsent(query.getClass(), key ->
|
||||
ClassUtils.getAllInterfacesForClass(key, this.proxyClassLoader));
|
||||
result = Proxy.newProxyInstance(this.proxyClassLoader, ifcs,
|
||||
new DeferredQueryInvocationHandler(query, target));
|
||||
isNewEm = false;
|
||||
}
|
||||
else {
|
||||
EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
finally {
|
||||
if (isNewEm) {
|
||||
EntityManagerFactoryUtils.closeEntityManager(target);
|
||||
}
|
||||
}
|
||||
return invokeMethod(method, args, target, newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invocation handler that delegates all calls to the current
|
||||
* transactional EntityAgent, if any; else, it will fall back
|
||||
* to a newly created EntityAgent per operation.
|
||||
* @since 7.0.4
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class SharedEntityAgentInvocationHandler extends SharedEntityHandlerInvocationHandler
|
||||
implements Serializable {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SharedEntityAgentInvocationHandler.class);
|
||||
|
||||
private transient volatile @Nullable ClassLoader proxyClassLoader;
|
||||
|
||||
public SharedEntityAgentInvocationHandler(EntityManagerFactory target, @Nullable Map<?, ?> properties) {
|
||||
super(target, properties);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
|
||||
// Rely on default serialization, just initialize state after deserialization.
|
||||
ois.defaultReadObject();
|
||||
// Initialize transient fields.
|
||||
initProxyClassLoader();
|
||||
@Override
|
||||
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// Invocation on EntityAgent interface coming in...
|
||||
|
||||
switch (method.getName()) {
|
||||
case "equals" -> {
|
||||
// Only consider equal when proxies are identical.
|
||||
return (proxy == args[0]);
|
||||
}
|
||||
case "hashCode" -> {
|
||||
// Use hashCode of EntityAgent proxy.
|
||||
return hashCode();
|
||||
}
|
||||
case "toString" -> {
|
||||
// Deliver toString without touching a target EntityAgent.
|
||||
return "Shared EntityAgent proxy for target factory [" + this.targetFactory + "]";
|
||||
}
|
||||
case "getEntityManagerFactory" -> {
|
||||
// JPA 2.0: return EntityManagerFactory without creating an EntityAgent.
|
||||
return this.targetFactory;
|
||||
}
|
||||
case "getCriteriaBuilder", "getMetamodel" -> {
|
||||
// JPA 2.0: return EntityManagerFactory's CriteriaBuilder/Metamodel (avoid creation of EntityAgent)
|
||||
try {
|
||||
return EntityManagerFactory.class.getMethod(method.getName()).invoke(this.targetFactory);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
case "unwrap" -> {
|
||||
// JPA 2.0: handle unwrap method - could be a proxy match.
|
||||
Class<?> targetClass = (Class<?>) args[0];
|
||||
if (targetClass != null && targetClass.isInstance(proxy)) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
case "isOpen" -> {
|
||||
// Handle isOpen method: always return true.
|
||||
return true;
|
||||
}
|
||||
case "close" -> {
|
||||
// Handle close method: suppress, not valid.
|
||||
return null;
|
||||
}
|
||||
case "getTransaction" -> {
|
||||
throw new IllegalStateException(
|
||||
"Not allowed to create transaction on shared EntityAgent - " +
|
||||
"use Spring transactions or EJB CMT instead");
|
||||
}
|
||||
}
|
||||
|
||||
// Determine current EntityAgent: either the transactional one
|
||||
// managed by the factory or a temporary one for the given invocation.
|
||||
Object target = EntityManagerFactoryUtils.doGetTransactionalEntityAgent(
|
||||
this.targetFactory, this.properties);
|
||||
|
||||
switch (method.getName()) {
|
||||
case "unwrap" -> {
|
||||
Class<?> targetClass = (Class<?>) args[0];
|
||||
if (targetClass == null) {
|
||||
return (target != null ? target : proxy);
|
||||
}
|
||||
// We need a transactional target now.
|
||||
if (target == null) {
|
||||
throw new IllegalStateException("No transactional EntityAgent available");
|
||||
}
|
||||
}
|
||||
// Still perform unwrap call on target EntityAgent.
|
||||
}
|
||||
|
||||
// Regular EntityAgent operations.
|
||||
boolean newTarget = false;
|
||||
if (target == null) {
|
||||
logger.debug("Creating new EntityAgent for shared EntityAgent invocation");
|
||||
target = EntityManagerFactoryUtils.createEntityAgent(this.targetFactory, this.properties);
|
||||
newTarget = true;
|
||||
}
|
||||
|
||||
// Invoke method on current EntityAgent.
|
||||
return invokeMethod(method, args, target, newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,13 +502,13 @@ public abstract class SharedEntityManagerCreator {
|
||||
|
||||
private final Query target;
|
||||
|
||||
private @Nullable EntityManager entityManager;
|
||||
private @Nullable Object entityHandler;
|
||||
|
||||
private @Nullable Map<@Nullable Object, @Nullable Object> outputParameters;
|
||||
|
||||
public DeferredQueryInvocationHandler(Query target, EntityManager entityManager) {
|
||||
public DeferredQueryInvocationHandler(Query target, Object entityHandler) {
|
||||
this.target = target;
|
||||
this.entityManager = entityManager;
|
||||
this.entityHandler = entityHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -395,7 +538,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
}
|
||||
}
|
||||
case "getOutputParameterValue" -> {
|
||||
if (this.entityManager == null) {
|
||||
if (this.entityHandler == null) {
|
||||
Object key = args[0];
|
||||
if (this.outputParameters == null || !this.outputParameters.containsKey(key)) {
|
||||
throw new IllegalArgumentException("OUT/INOUT parameter not available: " + key);
|
||||
@@ -426,7 +569,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
}
|
||||
finally {
|
||||
if (queryTerminatingMethods.contains(method.getName())) {
|
||||
// Actual execution of the query: close the EntityManager right
|
||||
// Actual execution of the query: close the EntityHandler right
|
||||
// afterwards, since that was the only reason we kept it open.
|
||||
if (this.outputParameters != null && this.target instanceof StoredProcedureQuery storedProc) {
|
||||
for (Map.Entry<Object, Object> entry : this.outputParameters.entrySet()) {
|
||||
@@ -444,8 +587,8 @@ public abstract class SharedEntityManagerCreator {
|
||||
}
|
||||
}
|
||||
}
|
||||
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
|
||||
this.entityManager = null;
|
||||
EntityManagerFactoryUtils.closeEntityHandler(this.entityHandler);
|
||||
this.entityHandler = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SessionHolder extends EntityManagerHolder {
|
||||
|
||||
private @Nullable StatelessSession statelessSession;
|
||||
|
||||
private @Nullable Transaction transaction;
|
||||
|
||||
private @Nullable FlushMode previousFlushMode;
|
||||
@@ -52,7 +50,7 @@ public class SessionHolder extends EntityManagerHolder {
|
||||
|
||||
SessionHolder(StatelessSession session) {
|
||||
super(null);
|
||||
this.statelessSession = session;
|
||||
setStatelessSession(session);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,16 +67,16 @@ public class SessionHolder extends EntityManagerHolder {
|
||||
}
|
||||
|
||||
void setStatelessSession(StatelessSession statelessSession) {
|
||||
this.statelessSession = statelessSession;
|
||||
this.entityAgent = statelessSession;
|
||||
}
|
||||
|
||||
StatelessSession getStatelessSession() {
|
||||
Assert.state(this.statelessSession != null, "No StatelessSession available");
|
||||
return this.statelessSession;
|
||||
Assert.state(this.entityAgent != null, "No StatelessSession available");
|
||||
return (StatelessSession) this.entityAgent;
|
||||
}
|
||||
|
||||
boolean hasStatelessSession() {
|
||||
return (this.statelessSession != null);
|
||||
return (this.entityAgent != null);
|
||||
}
|
||||
|
||||
void setTransaction(@Nullable Transaction transaction) {
|
||||
@@ -109,9 +107,6 @@ public class SessionHolder extends EntityManagerHolder {
|
||||
@Override
|
||||
protected void closeAll() {
|
||||
super.closeAll();
|
||||
if (this.statelessSession != null && this.statelessSession.isOpen()) {
|
||||
this.statelessSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-8
@@ -26,6 +26,7 @@ import jakarta.persistence.spi.PersistenceUnitInfo;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.StatelessSession;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
@@ -78,10 +79,6 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter implemen
|
||||
|
||||
private final PersistenceProvider persistenceProvider;
|
||||
|
||||
private final Class<? extends EntityManagerFactory> entityManagerFactoryInterface;
|
||||
|
||||
private final Class<? extends EntityManager> entityManagerInterface;
|
||||
|
||||
private @Nullable Object jtaTransactionManager;
|
||||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
@@ -89,8 +86,6 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter implemen
|
||||
|
||||
public HibernateJpaVendorAdapter() {
|
||||
this.persistenceProvider = new SpringHibernateJpaPersistenceProvider();
|
||||
this.entityManagerFactoryInterface = SessionFactory.class;
|
||||
this.entityManagerInterface = Session.class;
|
||||
}
|
||||
|
||||
|
||||
@@ -271,12 +266,17 @@ public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter implemen
|
||||
|
||||
@Override
|
||||
public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {
|
||||
return this.entityManagerFactoryInterface;
|
||||
return SessionFactory.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends EntityManager> getEntityManagerInterface() {
|
||||
return this.entityManagerInterface;
|
||||
return Session.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEntityAgentInterface() {
|
||||
return StatelessSession.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-6
@@ -108,7 +108,6 @@ class SharedEntityManagerCreatorTests {
|
||||
Query targetQuery = mock();
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(targetQuery);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
@@ -129,7 +128,6 @@ class SharedEntityManagerCreatorTests {
|
||||
Query targetQuery = mock();
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(targetQuery);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
@@ -150,7 +148,6 @@ class SharedEntityManagerCreatorTests {
|
||||
Query targetQuery = mock();
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(targetQuery);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
@@ -171,7 +168,6 @@ class SharedEntityManagerCreatorTests {
|
||||
Query targetQuery = mock();
|
||||
given(emf.createEntityManager()).willReturn(targetEm);
|
||||
given(targetEm.createQuery("x")).willReturn(targetQuery);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
@@ -194,7 +190,6 @@ class SharedEntityManagerCreatorTests {
|
||||
given(targetEm.createStoredProcedureQuery("x")).willReturn(targetQuery);
|
||||
willReturn("y").given(targetQuery).getOutputParameterValue(0);
|
||||
willReturn("z").given(targetQuery).getOutputParameterValue(2);
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
StoredProcedureQuery spq = em.createStoredProcedureQuery("x");
|
||||
@@ -225,7 +220,6 @@ class SharedEntityManagerCreatorTests {
|
||||
given(targetEm.createStoredProcedureQuery("x")).willReturn(targetQuery);
|
||||
willReturn("y").given(targetQuery).getOutputParameterValue("a");
|
||||
willReturn("z").given(targetQuery).getOutputParameterValue("c");
|
||||
given(targetEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
|
||||
StoredProcedureQuery spq = em.createStoredProcedureQuery("x");
|
||||
|
||||
-3
@@ -59,7 +59,6 @@ class PersistenceContextTransactionTests {
|
||||
void setup() {
|
||||
given(factory.createEntityManager()).willReturn(manager);
|
||||
given(manager.getTransaction()).willReturn(tx);
|
||||
given(manager.isOpen()).willReturn(true);
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
PersistenceAnnotationBeanPostProcessor pabpp = new PersistenceAnnotationBeanPostProcessor() {
|
||||
@@ -99,8 +98,6 @@ class PersistenceContextTransactionTests {
|
||||
|
||||
@Test
|
||||
void testTransactionCommitWithSharedEntityManagerAndPropagationSupports() {
|
||||
given(manager.isOpen()).willReturn(true);
|
||||
|
||||
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
|
||||
|
||||
tt.execute(status -> {
|
||||
|
||||
+1
-3
@@ -42,7 +42,6 @@ class SharedEntityManagerFactoryTests {
|
||||
Object o = new Object();
|
||||
|
||||
EntityManager mockEm = mock();
|
||||
given(mockEm.isOpen()).willReturn(true);
|
||||
|
||||
EntityManagerFactory mockEmf = mock();
|
||||
given(mockEmf.createEntityManager()).willReturn(mockEm);
|
||||
@@ -58,8 +57,7 @@ class SharedEntityManagerFactoryTests {
|
||||
assertThat(proxyFactoryBean.getObject()).isSameAs(proxy);
|
||||
assertThat(proxy.contains(o)).isFalse();
|
||||
|
||||
boolean condition = proxy instanceof EntityManagerProxy;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(proxy).isInstanceOf(EntityManagerProxy.class);
|
||||
EntityManagerProxy emProxy = (EntityManagerProxy) proxy;
|
||||
assertThatIllegalStateException().as("outside of transaction").isThrownBy(
|
||||
emProxy::getTargetEntityManager);
|
||||
|
||||
Reference in New Issue
Block a user