Browse Source

Add support for JPA 4.0 FlushModeType.EXPLICIT

Includes defensive nullable field access in EclipseLinkConnectionHandle.

Closes gh-36401
pull/36402/head
Juergen Hoeller 3 weeks ago
parent
commit
36815f4021
  1. 81
      spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java
  2. 3
      spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java
  3. 11
      spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java

81
spring-orm/src/main/java/org/springframework/orm/jpa/DefaultJpaDialect.java

@ -21,6 +21,7 @@ import java.sql.SQLException; @@ -21,6 +21,7 @@ import java.sql.SQLException;
import java.util.Map;
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.PersistenceException;
import org.jspecify.annotations.Nullable;
@ -37,6 +38,9 @@ import org.springframework.transaction.TransactionException; @@ -37,6 +38,9 @@ import org.springframework.transaction.TransactionException;
* <p>Simply begins a standard JPA transaction in {@link #beginTransaction} and
* performs standard exception translation through {@link EntityManagerFactoryUtils}.
*
* <p>Supports JPA 4.0's {@code FlushModeType.EXPLICIT} for read-only transactions,
* if available.
*
* @author Juergen Hoeller
* @since 2.0
* @see JpaTransactionManager#setJpaDialect
@ -44,16 +48,31 @@ import org.springframework.transaction.TransactionException; @@ -44,16 +48,31 @@ import org.springframework.transaction.TransactionException;
@SuppressWarnings("serial")
public class DefaultJpaDialect implements JpaDialect, Serializable {
// JPA 4.0 FlushModeType.EXPLICIT available?
private static final @Nullable FlushModeType FLUSH_MODE_EXPLICIT;
static {
FlushModeType explicit;
try {
explicit = FlushModeType.valueOf("EXPLICIT");
}
catch (IllegalArgumentException ex) {
explicit = null;
}
FLUSH_MODE_EXPLICIT = explicit;
}
/**
* This implementation invokes the standard JPA {@code Transaction.begin}
* method. Throws an InvalidIsolationLevelException if a non-default isolation
* level is set.
* <p>This implementation does not return any transaction data Object, since there
* is no state to be kept for a standard JPA transaction. Hence, subclasses do not
* have to care about the return value ({@code null}) of this implementation
* and are free to return their own transaction data Object.
* <p>This implementation returns transaction data for a flush mode reset
* if necessary, calling {@link #prepareFlushMode} accordingly. Can be reused
* in subclasses or alternatively replaced with custom flush mode handling.
* @see jakarta.persistence.EntityTransaction#begin
* @see org.springframework.transaction.InvalidIsolationLevelException
* @see #prepareFlushMode
* @see #cleanupTransaction
*/
@Override
@ -71,23 +90,52 @@ public class DefaultJpaDialect implements JpaDialect, Serializable { @@ -71,23 +90,52 @@ public class DefaultJpaDialect implements JpaDialect, Serializable {
}
entityManager.getTransaction().begin();
return null;
return prepareFlushMode(entityManager, definition.isReadOnly());
}
/**
* This implementation returns transaction data for a flush mode reset
* if necessary, calling {@link #prepareFlushMode} accordingly.
* @see #prepareFlushMode
*/
@Override
public @Nullable Object prepareTransaction(EntityManager entityManager, boolean readOnly, @Nullable String name)
throws PersistenceException {
return prepareFlushMode(entityManager, readOnly);
}
/**
* Prepare transaction data for a flush mode reset if necessary.
* Only applied for read-only transactions on JPA 4.0.
* <p>Used by {@link #beginTransaction} as well as {@link #prepareTransaction}.
* Can be reused in corresponding overridden methods in vendor-specific
* subclasses, or alternatively replaced with custom flush mode handling.
* @param entityManager the EntityManager to begin a JPA transaction on
* @param readOnly whether the transaction is supposed to be read-only
* @return transaction data for a flush mode reset, if necessary
* (to be returned from {@link #beginTransaction}/{@link #prepareTransaction}
* and subsequently passed into {@link #cleanupTransaction} after completion)
* @since 7.0.6
*/
protected @Nullable Object prepareFlushMode(EntityManager entityManager, boolean readOnly) {
if (readOnly && FLUSH_MODE_EXPLICIT != null) {
FlushModeType previousFlushMode = entityManager.getFlushMode();
entityManager.setFlushMode(FLUSH_MODE_EXPLICIT);
return new FlushModeTransactionData(entityManager, previousFlushMode);
}
return null;
}
/**
* This implementation does nothing, since the default {@code beginTransaction}
* implementation does not require any cleanup.
* @see #beginTransaction
* This implementation resets the flush mode if necessary.
* @see #prepareFlushMode
*/
@Override
public void cleanupTransaction(@Nullable Object transactionData) {
if (transactionData instanceof FlushModeTransactionData flushModeTransactionData) {
flushModeTransactionData.resetFlushMode();
}
}
/**
@ -149,4 +197,21 @@ public class DefaultJpaDialect implements JpaDialect, Serializable { @@ -149,4 +197,21 @@ public class DefaultJpaDialect implements JpaDialect, Serializable {
return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
}
private static class FlushModeTransactionData {
private final EntityManager entityManager;
private final FlushModeType previousFlushMode;
public FlushModeTransactionData(EntityManager entityManager, FlushModeType previousFlushMode) {
this.entityManager = entityManager;
this.previousFlushMode = previousFlushMode;
}
public void resetFlushMode() {
this.entityManager.setFlushMode(this.previousFlushMode);
}
}
}

3
spring-orm/src/main/java/org/springframework/orm/jpa/JpaDialect.java

@ -99,7 +99,7 @@ public interface JpaDialect extends PersistenceExceptionTranslator { @@ -99,7 +99,7 @@ public interface JpaDialect extends PersistenceExceptionTranslator {
* @param readOnly whether the transaction is supposed to be read-only
* @param name the name of the transaction (if any)
* @return an arbitrary object that holds transaction data, if any
* (to be passed into cleanupTransaction)
* (to be passed into {@link #cleanupTransaction})
* @throws jakarta.persistence.PersistenceException if thrown by JPA methods
* @see #cleanupTransaction
*/
@ -115,6 +115,7 @@ public interface JpaDialect extends PersistenceExceptionTranslator { @@ -115,6 +115,7 @@ public interface JpaDialect extends PersistenceExceptionTranslator {
* @param transactionData arbitrary object that holds transaction data, if any
* (as returned by beginTransaction or prepareTransaction)
* @see #beginTransaction
* @see #prepareTransaction
* @see org.springframework.jdbc.datasource.DataSourceUtils#resetConnectionAfterTransaction
*/
void cleanupTransaction(@Nullable Object transactionData);

11
spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java vendored

@ -153,7 +153,8 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { @@ -153,7 +153,8 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
entityManager.getTransaction().begin();
}
return null;
// Reuse JPA 4.0 FlushModeType.EXPLICIT handling from superclass.
return prepareFlushMode(entityManager, definition.isReadOnly());
}
@Override
@ -183,16 +184,18 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { @@ -183,16 +184,18 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
@Override
public Connection getConnection() {
if (this.connection == null) {
Connection con = this.connection;
if (con == null) {
transactionIsolationLock.lock();
try {
this.connection = this.entityManager.unwrap(Connection.class);
con = this.entityManager.unwrap(Connection.class);
}
finally {
transactionIsolationLock.unlock();
}
this.connection = con;
}
return this.connection;
return con;
}
}

Loading…
Cancel
Save