Browse Source

Early support for JPA 4.0 EntityAgent (autowiring a shared proxy)

Includes skipping of isOpen check on close where easily possible.

Closes gh-36025
pull/36273/head
Juergen Hoeller 1 month ago
parent
commit
cdbaa7f3a7
  1. 26
      spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java
  2. 136
      spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java
  3. 32
      spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java
  4. 14
      spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java
  5. 239
      spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java
  6. 15
      spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java
  7. 16
      spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java
  8. 6
      spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java
  9. 3
      spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceContextTransactionTests.java
  10. 4
      spring-orm/src/test/java/org/springframework/orm/jpa/support/SharedEntityManagerFactoryTests.java

26
spring-orm/src/main/java/org/springframework/orm/jpa/AbstractEntityManagerFactoryBean.java

@ -69,6 +69,7 @@ import org.springframework.util.CollectionUtils; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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);
}

136
spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java

@ -16,6 +16,7 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -523,7 +631,7 @@ public abstract class EntityManagerFactoryUtils {
@Override
protected void releaseResource(EntityManagerHolder resourceHolder, EntityManagerFactory resourceKey) {
closeEntityManager(resourceHolder.getEntityManager());
resourceHolder.closeAll();
}
}

32
spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerHolder.java

@ -39,6 +39,8 @@ public class EntityManagerHolder extends ResourceHolderSupport { @@ -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 { @@ -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 { @@ -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;
}
}

14
spring-orm/src/main/java/org/springframework/orm/jpa/JpaVendorAdapter.java

@ -124,6 +124,20 @@ public interface JpaVendorAdapter { @@ -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.

239
spring-orm/src/main/java/org/springframework/orm/jpa/SharedEntityManagerCreator.java

@ -177,31 +177,42 @@ public abstract class SharedEntityManagerCreator { @@ -177,31 +177,42 @@ public abstract class SharedEntityManagerCreator {
ifcs, new SharedEntityManagerInvocationHandler(emf, properties, synchronizedWithTransaction));
}
/**
* 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.
* 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
*/
@SuppressWarnings("serial")
private static class SharedEntityManagerInvocationHandler implements InvocationHandler, Serializable {
private static final Log logger = LogFactory.getLog(SharedEntityManagerInvocationHandler.class);
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));
}
private final EntityManagerFactory targetFactory;
private final @Nullable Map<?, ?> properties;
/**
* Base class for EntityManager and EntityAgent invocation handlers.
* @since 7.0.4
*/
@SuppressWarnings("serial")
private abstract static class SharedEntityHandlerInvocationHandler implements InvocationHandler, Serializable {
private final boolean synchronizedWithTransaction;
protected final EntityManagerFactory targetFactory;
private transient volatile @Nullable ClassLoader proxyClassLoader;
protected final @Nullable Map<?, ?> properties;
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 { @@ -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 { @@ -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;
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);
}
@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);
}
else {
EntityManagerFactoryUtils.applyTransactionTimeout(query, this.targetFactory);
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
return result;
}
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");
}
}
finally {
if (isNewEm) {
EntityManagerFactoryUtils.closeEntityManager(target);
// 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.
}
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// Rely on default serialization, just initialize state after deserialization.
ois.defaultReadObject();
// Initialize transient fields.
initProxyClassLoader();
// 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 { @@ -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 { @@ -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 { @@ -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 { @@ -444,8 +587,8 @@ public abstract class SharedEntityManagerCreator {
}
}
}
EntityManagerFactoryUtils.closeEntityManager(this.entityManager);
this.entityManager = null;
EntityManagerFactoryUtils.closeEntityHandler(this.entityHandler);
this.entityHandler = null;
}
}
}

15
spring-orm/src/main/java/org/springframework/orm/jpa/hibernate/SessionHolder.java

@ -39,8 +39,6 @@ import org.springframework.util.Assert; @@ -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 { @@ -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 { @@ -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 { @@ -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();
}
}
}

16
spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java vendored

@ -26,6 +26,7 @@ import jakarta.persistence.spi.PersistenceUnitInfo; @@ -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 @@ -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 @@ -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 @@ -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
spring-orm/src/test/java/org/springframework/orm/jpa/SharedEntityManagerCreatorTests.java

@ -108,7 +108,6 @@ class SharedEntityManagerCreatorTests { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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
spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceContextTransactionTests.java

@ -59,7 +59,6 @@ class PersistenceContextTransactionTests { @@ -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 { @@ -99,8 +98,6 @@ class PersistenceContextTransactionTests {
@Test
void testTransactionCommitWithSharedEntityManagerAndPropagationSupports() {
given(manager.isOpen()).willReturn(true);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
tt.execute(status -> {

4
spring-orm/src/test/java/org/springframework/orm/jpa/support/SharedEntityManagerFactoryTests.java

@ -42,7 +42,6 @@ class SharedEntityManagerFactoryTests { @@ -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 { @@ -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);

Loading…
Cancel
Save