HibernateJpaDialect/HibernateTransactionManager alignment for 5.2+

See gh-25533
This commit is contained in:
Juergen Hoeller 2020-08-06 12:00:20 +02:00
parent df98d0c661
commit c99c3142ad
8 changed files with 66 additions and 145 deletions

View File

@ -458,7 +458,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
@SuppressWarnings("deprecation")
protected void doBegin(Object transaction, TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
@ -470,7 +469,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
"on a single DataSource, no matter whether Hibernate or JDBC access.");
}
Session session = null;
SessionImplementor session = null;
try {
if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
@ -487,17 +486,18 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
txObject.setSession(newSession);
}
session = txObject.getSessionHolder().getSession();
session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals(
session.getJdbcCoordinator().getLogicalConnection().getConnectionHandlingMode().getReleaseMode())) {
// We're allowed to change the transaction settings of the JDBC Connection.
if (logger.isDebugEnabled()) {
logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
}
Connection con = ((SessionImplementor) session).connection();
Connection con = session.connection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
@ -508,6 +508,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
}
}
txObject.connectionPrepared();
}
else {
// Not allowed to change the transaction settings of the JDBC Connection.
@ -516,7 +517,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
throw new InvalidIsolationLevelException(
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
"Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
"Hibernate connection release mode is set to ON_CLOSE.");
}
if (logger.isDebugEnabled()) {
logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
@ -526,7 +527,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
if (definition.isReadOnly() && txObject.isNewSession()) {
// Just set to MANUAL in case of a new Session for this transaction.
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
// As of 5.1, we're also setting Hibernate's read-only entity mode by default.
session.setDefaultReadOnly(true);
}
@ -535,7 +536,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
// We need AUTO or COMMIT for a non-read-only transaction.
FlushMode flushMode = session.getHibernateFlushMode();
if (FlushMode.MANUAL.equals(flushMode)) {
session.setFlushMode(FlushMode.AUTO);
session.setHibernateFlushMode(FlushMode.AUTO);
txObject.getSessionHolder().setPreviousFlushMode(flushMode);
}
}
@ -561,8 +562,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
if (getDataSource() != null) {
SessionImplementor sessionImpl = (SessionImplementor) session;
ConnectionHolder conHolder = new ConnectionHolder(sessionImpl::connection);
ConnectionHolder conHolder = new ConnectionHolder(session::connection);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
conHolder.setTimeoutInSeconds(timeout);
}
@ -704,7 +704,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
}
@Override
@SuppressWarnings("deprecation")
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
@ -718,13 +717,14 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
TransactionSynchronizationManager.unbindResource(getDataSource());
}
Session session = txObject.getSessionHolder().getSession();
if (this.prepareConnection && isPhysicallyConnected(session)) {
// We're running with connection release mode "on_close": We're able to reset
SessionImplementor session = txObject.getSessionHolder().getSession().unwrap(SessionImplementor.class);
if (txObject.needsConnectionReset() &&
session.getJdbcCoordinator().getLogicalConnection().isPhysicallyConnected()) {
// We're running with connection release mode ON_CLOSE: We're able to reset
// the isolation level and/or read-only flag of the JDBC Connection here.
// Else, we need to rely on the connection pool to perform proper cleanup.
try {
Connection con = ((SessionImplementor) session).connection();
Connection con = session.connection();
Integer previousHoldability = txObject.getPreviousHoldability();
if (previousHoldability != null) {
con.setHoldability(previousHoldability);
@ -751,7 +751,7 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
}
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
session.setHibernateFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
}
if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
disconnectOnCompletion(session);
@ -772,41 +772,6 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
session.disconnect();
}
/**
* Return whether the given Hibernate Session will always hold the same
* JDBC Connection. This is used to check whether the transaction manager
* can safely prepare and clean up the JDBC Connection used for a transaction.
* <p>The default implementation checks the Session's connection release mode
* to be "on_close".
* @param session the Hibernate Session to check
* @see ConnectionReleaseMode#ON_CLOSE
*/
@SuppressWarnings("deprecation")
protected boolean isSameConnectionForEntireSession(Session session) {
if (!(session instanceof SessionImplementor)) {
// The best we can do is to assume we're safe.
return true;
}
ConnectionReleaseMode releaseMode =
((SessionImplementor) session).getJdbcCoordinator().getConnectionReleaseMode();
return ConnectionReleaseMode.ON_CLOSE.equals(releaseMode);
}
/**
* Determine whether the given Session is (still) physically connected
* to the database, that is, holds an active JDBC Connection internally.
* @param session the Hibernate Session to check
* @see #isSameConnectionForEntireSession(Session)
*/
protected boolean isPhysicallyConnected(Session session) {
if (!(session instanceof SessionImplementor)) {
// The best we can do is to check whether we're logically connected.
return session.isConnected();
}
return ((SessionImplementor) session).getJdbcCoordinator().getLogicalConnection().isPhysicallyConnected();
}
/**
* Convert the given HibernateException to an appropriate exception
* from the {@code org.springframework.dao} hierarchy.
@ -834,6 +799,8 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
private boolean newSession;
private boolean needsConnectionReset;
@Nullable
private Integer previousHoldability;
@ -872,6 +839,14 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
return this.newSession;
}
public void connectionPrepared() {
this.needsConnectionReset = true;
}
public boolean needsConnectionReset() {
return this.needsConnectionReset;
}
public void setPreviousHoldability(@Nullable Integer previousHoldability) {
this.previousHoldability = previousHoldability;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@ -38,11 +38,10 @@ public class SpringJtaSessionContext extends JTASessionContext {
}
@Override
@SuppressWarnings("deprecation")
protected Session buildOrObtainSession() {
Session session = super.buildOrObtainSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
}
return session;
}

View File

@ -80,7 +80,6 @@ public class SpringSessionContext implements CurrentSessionContext {
* Retrieve the Spring-managed Session for the current thread, if any.
*/
@Override
@SuppressWarnings("deprecation")
public Session currentSession() throws HibernateException {
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
if (value instanceof Session) {
@ -100,7 +99,7 @@ public class SpringSessionContext implements CurrentSessionContext {
FlushMode flushMode = session.getHibernateFlushMode();
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO);
session.setHibernateFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
@ -130,7 +129,7 @@ public class SpringSessionContext implements CurrentSessionContext {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(

View File

@ -98,13 +98,12 @@ public class SpringSessionSynchronization implements TransactionSynchronization,
}
@Override
@SuppressWarnings("deprecation")
public void beforeCompletion() {
try {
Session session = this.sessionHolder.getSession();
if (this.sessionHolder.getPreviousFlushMode() != null) {
// In case of pre-bound Session, restore previous flush mode.
session.setFlushMode(this.sessionHolder.getPreviousFlushMode());
session.setHibernateFlushMode(this.sessionHolder.getPreviousFlushMode());
}
// Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
session.disconnect();

View File

@ -204,11 +204,10 @@ public class OpenSessionInViewFilter extends OncePerRequestFilter {
* @throws DataAccessResourceFailureException if the Session could not be created
* @see FlushMode#MANUAL
*/
@SuppressWarnings("deprecation")
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
try {
Session session = sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
return session;
}
catch (HibernateException ex) {

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.
@ -186,11 +186,10 @@ public class OpenSessionInViewInterceptor implements AsyncWebRequestInterceptor
* @throws DataAccessResourceFailureException if the Session could not be created
* @see FlushMode#MANUAL
*/
@SuppressWarnings("deprecation")
protected Session openSession() throws DataAccessResourceFailureException {
try {
Session session = obtainSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
return session;
}
catch (HibernateException ex) {

View File

@ -111,13 +111,12 @@ public class OpenSessionInterceptor implements MethodInterceptor, InitializingBe
* @since 5.0
* @see FlushMode#MANUAL
*/
@SuppressWarnings("deprecation")
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = openSession();
if (session == null) {
try {
session = sessionFactory.openSession();
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);

View File

@ -16,14 +16,13 @@
package org.springframework.orm.jpa.vendor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import org.apache.commons.logging.LogFactory;
import org.hibernate.ConnectionReleaseMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
@ -43,6 +42,7 @@ import org.hibernate.UnresolvableObjectException;
import org.hibernate.WrongClassException;
import org.hibernate.dialect.lock.OptimisticEntityLockException;
import org.hibernate.dialect.lock.PessimisticEntityLockException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.DataException;
import org.hibernate.exception.JDBCConnectionException;
@ -71,12 +71,10 @@ import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.ResourceTransactionDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link org.springframework.orm.jpa.JpaDialect} implementation for
* Hibernate EntityManager. Developed against Hibernate 5.1/5.2/5.3/5.4.
* Hibernate EntityManager. Developed against Hibernate 5.2/5.3/5.4.
*
* @author Juergen Hoeller
* @author Costin Leau
@ -88,27 +86,6 @@ import org.springframework.util.ReflectionUtils;
@SuppressWarnings("serial")
public class HibernateJpaDialect extends DefaultJpaDialect {
private static Method getFlushMode;
static {
try {
// Hibernate 5.2+ getHibernateFlushMode()
getFlushMode = Session.class.getMethod("getHibernateFlushMode");
}
catch (NoSuchMethodException ex) {
try {
// Classic Hibernate getFlushMode() with FlushMode return type
getFlushMode = Session.class.getMethod("getFlushMode");
}
catch (NoSuchMethodException ex2) {
throw new IllegalStateException("No compatible Hibernate getFlushMode signature found", ex2);
}
}
// Check that it is the Hibernate FlushMode type, not JPA's...
Assert.state(FlushMode.class == getFlushMode.getReturnType(), "Could not find Hibernate getFlushMode method");
}
boolean prepareConnection = true;
@Nullable
@ -159,7 +136,7 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
throws PersistenceException, SQLException, TransactionException {
Session session = getSession(entityManager);
SessionImplementor session = getSession(entityManager);
if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
session.getTransaction().setTimeout(definition.getTimeout());
@ -170,13 +147,16 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
Connection preparedCon = null;
if (isolationLevelNeeded || definition.isReadOnly()) {
if (this.prepareConnection) {
preparedCon = HibernateConnectionHandle.doGetConnection(session);
if (this.prepareConnection && ConnectionReleaseMode.ON_CLOSE.equals(
session.getJdbcCoordinator().getLogicalConnection().getConnectionHandlingMode().getReleaseMode())) {
preparedCon = session.connection();
previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(preparedCon, definition);
}
else if (isolationLevelNeeded) {
throw new InvalidIsolationLevelException(getClass().getSimpleName() +
" does not support custom isolation levels since the 'prepareConnection' flag is off.");
throw new InvalidIsolationLevelException(
"HibernateJpaDialect is not allowed to support custom isolation levels: " +
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
"Hibernate connection release mode is set to ON_CLOSE.");
}
}
@ -195,34 +175,32 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
}
}
return new SessionTransactionData(
session, previousFlushMode, preparedCon, previousIsolationLevel, definition.isReadOnly());
session, previousFlushMode, (preparedCon != null), previousIsolationLevel, definition.isReadOnly());
}
@Override
public Object prepareTransaction(EntityManager entityManager, boolean readOnly, @Nullable String name)
throws PersistenceException {
Session session = getSession(entityManager);
SessionImplementor session = getSession(entityManager);
FlushMode previousFlushMode = prepareFlushMode(session, readOnly);
return new SessionTransactionData(session, previousFlushMode, null, null, readOnly);
return new SessionTransactionData(session, previousFlushMode, false, null, readOnly);
}
@SuppressWarnings("deprecation")
@Nullable
protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
FlushMode flushMode = (FlushMode) ReflectionUtils.invokeMethod(getFlushMode, session);
Assert.state(flushMode != null, "No FlushMode from Session");
FlushMode flushMode = session.getHibernateFlushMode();
if (readOnly) {
// We should suppress flushing for a read-only transaction.
if (!flushMode.equals(FlushMode.MANUAL)) {
session.setFlushMode(FlushMode.MANUAL);
session.setHibernateFlushMode(FlushMode.MANUAL);
return flushMode;
}
}
else {
// We need AUTO or COMMIT for a non-read-only transaction.
if (flushMode.lessThan(FlushMode.COMMIT)) {
session.setFlushMode(FlushMode.AUTO);
session.setHibernateFlushMode(FlushMode.AUTO);
return flushMode;
}
}
@ -241,7 +219,7 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly)
throws PersistenceException, SQLException {
Session session = getSession(entityManager);
SessionImplementor session = getSession(entityManager);
return new HibernateConnectionHandle(session);
}
@ -353,32 +331,31 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
return new JpaSystemException(ex);
}
protected Session getSession(EntityManager entityManager) {
return entityManager.unwrap(Session.class);
protected SessionImplementor getSession(EntityManager entityManager) {
return entityManager.unwrap(SessionImplementor.class);
}
private static class SessionTransactionData {
private final Session session;
private final SessionImplementor session;
@Nullable
private final FlushMode previousFlushMode;
@Nullable
private final Connection preparedCon;
private final boolean needsConnectionReset;
@Nullable
private final Integer previousIsolationLevel;
private final boolean readOnly;
public SessionTransactionData(Session session, @Nullable FlushMode previousFlushMode,
@Nullable Connection preparedCon, @Nullable Integer previousIsolationLevel, boolean readOnly) {
public SessionTransactionData(SessionImplementor session, @Nullable FlushMode previousFlushMode,
boolean connectionPrepared, @Nullable Integer previousIsolationLevel, boolean readOnly) {
this.session = session;
this.previousFlushMode = previousFlushMode;
this.preparedCon = preparedCon;
this.needsConnectionReset = connectionPrepared;
this.previousIsolationLevel = previousIsolationLevel;
this.readOnly = readOnly;
}
@ -388,14 +365,9 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
if (this.previousFlushMode != null) {
this.session.setFlushMode(this.previousFlushMode);
}
if (this.preparedCon != null && this.session.isConnected()) {
Connection conToReset = HibernateConnectionHandle.doGetConnection(this.session);
if (conToReset != this.preparedCon) {
LogFactory.getLog(HibernateJpaDialect.class).warn(
"JDBC Connection to reset not identical to originally prepared Connection - please " +
"make sure to use connection release mode ON_CLOSE (the default) and to run against " +
"Hibernate 4.2+ (or switch HibernateJpaDialect's prepareConnection flag to false");
}
if (this.needsConnectionReset &&
this.session.getJdbcCoordinator().getLogicalConnection().isPhysicallyConnected()) {
Connection conToReset = this.session.connection();
DataSourceUtils.resetConnectionAfterTransaction(
conToReset, this.previousIsolationLevel, this.readOnly);
}
@ -405,35 +377,15 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
private static class HibernateConnectionHandle implements ConnectionHandle {
@Nullable
private static volatile Method connectionMethodToUse;
private final SessionImplementor session;
private final Session session;
public HibernateConnectionHandle(Session session) {
public HibernateConnectionHandle(SessionImplementor session) {
this.session = session;
}
@Override
public Connection getConnection() {
return doGetConnection(this.session);
}
public static Connection doGetConnection(Session session) {
try {
Method methodToUse = connectionMethodToUse;
if (methodToUse == null) {
// Reflective lookup to find SessionImpl's connection() method on Hibernate 4.x/5.x
methodToUse = session.getClass().getMethod("connection");
connectionMethodToUse = methodToUse;
}
Connection con = (Connection) ReflectionUtils.invokeMethod(methodToUse, session);
Assert.state(con != null, "No Connection from Session");
return con;
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("Cannot find connection() method on Hibernate Session", ex);
}
return this.session.connection();
}
}