HHH-10664 - Prep 6.0 feature branch - merge hibernate-entitymanager into hibernate-core (HEM tests - JPA requirements around access to Transaction delegate after EntityManager is closed; also its requirement that the same Transaction delegate be available across all calls to its commit/rollback methods)

This commit is contained in:
Steve Ebersole 2016-04-27 12:26:32 -05:00
parent 8e6d51d56d
commit 2c96937587
12 changed files with 114 additions and 44 deletions

View File

@ -476,8 +476,8 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
} }
@Override @Override
public void markAsJpaBootstrap(boolean jpaBootstrap) { public void markAsJpaBootstrap() {
this.options.jpaBootstrap = jpaBootstrap; this.options.jpaBootstrap = true;
} }
@Override @Override

View File

@ -19,6 +19,7 @@ import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.SchemaAutoTooling; import org.hibernate.boot.SchemaAutoTooling;
import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cache.spi.QueryCacheFactory; import org.hibernate.cache.spi.QueryCacheFactory;
import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; import org.hibernate.cfg.BaselineSessionEventsListenerBuilder;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
@ -38,6 +39,11 @@ import org.hibernate.tuple.entity.EntityTuplizerFactory;
public interface SessionFactoryOptionsState { public interface SessionFactoryOptionsState {
StandardServiceRegistry getServiceRegistry(); StandardServiceRegistry getServiceRegistry();
/**
* @deprecated (since 5.2) see {@link SessionFactoryOptions#isJpaBootstrap} for details
* on deprecation and intention/use.
*/
@Deprecated
boolean isJpaBootstrap(); boolean isJpaBootstrap();
Object getBeanManagerReference(); Object getBeanManagerReference();

View File

@ -15,6 +15,23 @@ import org.hibernate.boot.SessionFactoryBuilder;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface SessionFactoryBuilderImplementor extends SessionFactoryBuilder { public interface SessionFactoryBuilderImplementor extends SessionFactoryBuilder {
void markAsJpaBootstrap(boolean jpaBootstrap); /**
* Indicates that the SessionFactory being built comes from JPA bootstrapping.
* Internally {@code false} is the assumed value. We only need to call this to
* mark that as true.
*
* @deprecated (since 5.2) In fact added in 5.2 as part of consolidating JPA support
* directly into Hibernate contracts (SessionFactory, Session); intended to provide
* transition help in cases where we need to know the difference in JPA/native use for
* various reasons.
*/
@Deprecated
void markAsJpaBootstrap();
/**
* Build the SessionFactoryOptions that will ultimately be passed to SessionFactoryImpl constructor.
*
* @return The options.
*/
SessionFactoryOptions buildSessionFactoryOptions(); SessionFactoryOptions buildSessionFactoryOptions();
} }

View File

@ -43,12 +43,21 @@ public interface SessionFactoryOptions {
*/ */
StandardServiceRegistry getServiceRegistry(); StandardServiceRegistry getServiceRegistry();
boolean isJpaBootstrap();
Object getBeanManagerReference(); Object getBeanManagerReference();
Object getValidatorFactoryReference(); Object getValidatorFactoryReference();
/**
* @deprecated (since 5.2) In fact added in 5.2 as part of consolidating JPA support
* directly into Hibernate contracts (SessionFactory, Session); intended to provide
* transition help in cases where we need to know the difference in JPA/native use for
* various reasons.
*
* @see SessionFactoryBuilderImplementor#markAsJpaBootstrap
*/
@Deprecated
boolean isJpaBootstrap();
/** /**
* The name to be used for the SessionFactory. This is use both in:<ul> * The name to be used for the SessionFactory. This is use both in:<ul>
* <li>in-VM serialization</li> * <li>in-VM serialization</li>

View File

@ -27,21 +27,21 @@ public class TransactionImpl implements TransactionImplementor {
private static final Logger LOG = CoreLogging.logger( TransactionImpl.class ); private static final Logger LOG = CoreLogging.logger( TransactionImpl.class );
private final TransactionCoordinator transactionCoordinator; private final TransactionCoordinator transactionCoordinator;
private final TransactionDriver transactionDriverControl; private TransactionDriver transactionDriverControl;
private boolean valid = true;
public TransactionImpl(TransactionCoordinator transactionCoordinator) { public TransactionImpl(TransactionCoordinator transactionCoordinator) {
this.transactionCoordinator = transactionCoordinator; this.transactionCoordinator = transactionCoordinator;
this.transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
} }
@Override @Override
public void begin() { public void begin() {
if ( !valid ) { if ( !transactionCoordinator.isActive() ) {
throw new TransactionException( "Transaction instance is no longer valid" ); throw new TransactionException( "Cannot begin Transaction on closed Session/EntityManager" );
} }
if ( transactionDriverControl == null ) {
transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
}
// per-JPA // per-JPA
if ( isActive() ) { if ( isActive() ) {
throw new IllegalStateException( "Transaction already active" ); throw new IllegalStateException( "Transaction already active" );
@ -61,13 +61,21 @@ public class TransactionImpl implements TransactionImplementor {
LOG.debug( "committing" ); LOG.debug( "committing" );
try { try {
this.transactionDriverControl.commit(); internalGetTransactionDriverControl().commit();
} }
finally { finally {
invalidate(); // invalidate();
} }
} }
public TransactionDriver internalGetTransactionDriverControl() {
// NOTE here to help be a more descriptive NullPointerException
if ( this.transactionDriverControl == null ) {
throw new IllegalStateException( "Transaction was not properly begun/started" );
}
return this.transactionDriverControl;
}
@Override @Override
public void rollback() { public void rollback() {
// todo : may need a "JPA compliant" flag here // todo : may need a "JPA compliant" flag here
@ -76,7 +84,7 @@ public class TransactionImpl implements TransactionImplementor {
if ( status == TransactionStatus.ROLLED_BACK || status == TransactionStatus.NOT_ACTIVE ) { if ( status == TransactionStatus.ROLLED_BACK || status == TransactionStatus.NOT_ACTIVE ) {
// Allow rollback() calls on completed transactions, just no-op. // Allow rollback() calls on completed transactions, just no-op.
LOG.debug( "rollback() called on an inactive transaction" ); LOG.debug( "rollback() called on an inactive transaction" );
invalidate(); // invalidate();
return; return;
} }
@ -87,7 +95,7 @@ public class TransactionImpl implements TransactionImplementor {
LOG.debug( "rolling back" ); LOG.debug( "rolling back" );
if ( status != TransactionStatus.FAILED_COMMIT || allowFailedCommitToPhysicallyRollback() ) { if ( status != TransactionStatus.FAILED_COMMIT || allowFailedCommitToPhysicallyRollback() ) {
try { try {
this.transactionDriverControl.rollback(); internalGetTransactionDriverControl().rollback();
} }
finally { finally {
invalidate(); invalidate();
@ -102,7 +110,10 @@ public class TransactionImpl implements TransactionImplementor {
@Override @Override
public TransactionStatus getStatus() { public TransactionStatus getStatus() {
return transactionDriverControl.getStatus(); // Allow looking at STATUS on closed Session/Transaction
return transactionDriverControl == null
? TransactionStatus.NOT_ACTIVE
: transactionDriverControl.getStatus();
} }
@Override @Override
@ -122,7 +133,7 @@ public class TransactionImpl implements TransactionImplementor {
@Override @Override
public void setRollbackOnly() { public void setRollbackOnly() {
transactionDriverControl.markRollbackOnly(); internalGetTransactionDriverControl().markRollbackOnly();
} }
@Override @Override
@ -130,10 +141,6 @@ public class TransactionImpl implements TransactionImplementor {
return getStatus() == TransactionStatus.MARKED_ROLLBACK; return getStatus() == TransactionStatus.MARKED_ROLLBACK;
} }
public void invalidate() {
valid = false;
}
protected boolean allowFailedCommitToPhysicallyRollback() { protected boolean allowFailedCommitToPhysicallyRollback() {
return false; return false;
} }

View File

@ -18,6 +18,14 @@ public interface TransactionImplementor extends Transaction {
* <li>The transaction is rolled-back</li> * <li>The transaction is rolled-back</li>
* <li>The session that owns the transaction is closed</li> * <li>The session that owns the transaction is closed</li>
* </ul> * </ul>
*
* @deprecated (since 5.2) as part of effort to consolidate support for JPA and Hibernate SessionFactory, Session, etc
* natively, support for local Transaction delegates to remain "valid" after they are committed or rolled-back (and to a
* degree after the owning Session is closed) to more closely comply with the JPA spec natively in terms
* of allowing that extended access after Session is closed. Hibernate impls have all been changed to no-op here.
*/ */
void invalidate(); @Deprecated
default void invalidate() {
// no-op : see @deprecated note
}
} }

View File

@ -369,16 +369,35 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
@Override @Override
public Transaction getTransaction() throws HibernateException { public Transaction getTransaction() throws HibernateException {
// See class-level JavaDocs for a discussion of the concurrent-access safety of this method if ( getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
// JPA requires that we throw IllegalStateException if this is called
// on a JTA EntityManager
//
// todo : ultimately add an option for allowing users to access the Transaction in JTA cases too like classic Hibernate
if ( getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() ) {
throw new IllegalStateException( "A JTA EntityManager cannot use getTransaction()" );
}
if ( this.currentHibernateTransaction == null ) {
this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator() );
}
if ( !isClosed() ) {
getTransactionCoordinator().pulse();
}
return currentHibernateTransaction;
}
else {
// Historically Hibernate would not allow access to the Transaction after the Session is closed
checkOpen(); checkOpen();
// todo : determine whether this is allowed for JPA scenarios based on PersistenceUnitTransactionType && "strictness"
if ( this.currentHibernateTransaction == null || this.currentHibernateTransaction.getStatus() != TransactionStatus.ACTIVE ) { if ( this.currentHibernateTransaction == null || this.currentHibernateTransaction.getStatus() != TransactionStatus.ACTIVE ) {
this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator() ); this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator() );
} }
getTransactionCoordinator().pulse(); getTransactionCoordinator().pulse();
return currentHibernateTransaction; return currentHibernateTransaction;
} }
}
@Override @Override
public Transaction beginTransaction() { public Transaction beginTransaction() {

View File

@ -158,7 +158,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
private final String name; private final String name;
private final String uuid; private final String uuid;
private final boolean jpaBootstrap;
private transient boolean isClosed; private transient boolean isClosed;
private final transient SessionFactoryObserverChain observer = new SessionFactoryObserverChain(); private final transient SessionFactoryObserverChain observer = new SessionFactoryObserverChain();
@ -221,7 +220,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor {
catch (Exception e) { catch (Exception e) {
throw new AssertionFailure("Could not generate UUID"); throw new AssertionFailure("Could not generate UUID");
} }
this.jpaBootstrap = options.isJpaBootstrap();
final JdbcServices jdbcServices = serviceRegistry.getService( JdbcServices.class ); final JdbcServices jdbcServices = serviceRegistry.getService( JdbcServices.class );

View File

@ -869,7 +869,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
} }
protected void populate(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) { protected void populate(SessionFactoryBuilder sfBuilder, StandardServiceRegistry ssr) {
( ( SessionFactoryBuilderImplementor) sfBuilder ).markAsJpaBootstrap( true ); ( ( SessionFactoryBuilderImplementor) sfBuilder ).markAsJpaBootstrap();
final StrategySelector strategySelector = ssr.getService( StrategySelector.class ); final StrategySelector strategySelector = ssr.getService( StrategySelector.class );

View File

@ -56,14 +56,12 @@ import org.hibernate.jpa.internal.util.LockModeTypeHelper;
import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies; import org.hibernate.property.access.spi.BuiltInPropertyAccessStrategies;
import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.proxy.HibernateProxyHelper;
import org.hibernate.query.ParameterMetadata; import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.QueryParameter; import org.hibernate.query.QueryParameter;
import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.SerializableType; import org.hibernate.type.SerializableType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type; import org.hibernate.type.Type;
import static org.hibernate.jpa.QueryHints.HINT_CACHEABLE; import static org.hibernate.jpa.QueryHints.HINT_CACHEABLE;
@ -1132,6 +1130,20 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
try { try {
return doExecuteUpdate(); return doExecuteUpdate();
} }
catch ( QueryExecutionRequestException e) {
throw new IllegalStateException( e );
}
catch( TypeMismatchException e ) {
throw new IllegalArgumentException( e );
}
catch ( HibernateException e) {
if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
throw getProducer().convert( e );
}
else {
throw e;
}
}
finally { finally {
afterQuery(); afterQuery();
} }

View File

@ -168,16 +168,6 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC
for ( TransactionObserver observer : observers ) { for ( TransactionObserver observer : observers ) {
observer.afterCompletion( successful, false ); observer.afterCompletion( successful, false );
} }
invalidateDelegate();
}
private void invalidateDelegate() {
if ( physicalTransactionDelegate == null ) {
throw new IllegalStateException( "Physical-transaction delegate not known on attempt to invalidate" );
}
physicalTransactionDelegate.invalidate();
physicalTransactionDelegate = null;
} }
public void addObserver(TransactionObserver observer) { public void addObserver(TransactionObserver observer) {

View File

@ -284,6 +284,10 @@ public abstract class BaseEntityManagerFunctionalTestCase extends BaseUnitTestCa
if ( em == null ) { if ( em == null ) {
return; return;
} }
if ( !em.isOpen() ) {
return;
}
if ( em.getTransaction().isActive() ) { if ( em.getTransaction().isActive() ) {
em.getTransaction().rollback(); em.getTransaction().rollback();
log.warn("You left an open transaction! Fix your test case. For now, we are closing it for you."); log.warn("You left an open transaction! Fix your test case. For now, we are closing it for you.");