/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.engine.transaction.internal;
import javax.transaction.Synchronization;
import org.hibernate.HibernateException;
import org.hibernate.TransactionException;
import org.hibernate.engine.spi.ExceptionConverter;
import org.hibernate.engine.transaction.spi.TransactionImplementor;
import org.hibernate.internal.AbstractSharedSessionContract;
import org.hibernate.internal.CoreLogging;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.jboss.logging.Logger;
import static org.hibernate.resource.transaction.spi.TransactionCoordinator.TransactionDriver;
Author: Andrea Boriero, Steve Ebersole
/**
* @author Andrea Boriero
* @author Steve Ebersole
*/
public class TransactionImpl implements TransactionImplementor {
private static final Logger LOG = CoreLogging.logger( TransactionImpl.class );
private final TransactionCoordinator transactionCoordinator;
private final ExceptionConverter exceptionConverter;
private final JpaCompliance jpaCompliance;
private final AbstractSharedSessionContract session;
private TransactionDriver transactionDriverControl;
public TransactionImpl(
TransactionCoordinator transactionCoordinator,
ExceptionConverter exceptionConverter,
AbstractSharedSessionContract session) {
this.transactionCoordinator = transactionCoordinator;
this.exceptionConverter = exceptionConverter;
this.jpaCompliance = session.getFactory().getSessionFactoryOptions().getJpaCompliance();
this.session = session;
if ( session.isOpen() && transactionCoordinator.isActive() ) {
this.transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
}
else {
LOG.debug( "TransactionImpl created on closed Session/EntityManager" );
}
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == %s",
jpaCompliance.isJpaTransactionComplianceEnabled()
);
}
}
@Override
public void begin() {
if ( !session.isOpen() ) {
throw new IllegalStateException( "Cannot begin Transaction on closed Session/EntityManager" );
}
if ( transactionDriverControl == null ) {
transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
}
// per-JPA
if ( isActive() ) {
throw new IllegalStateException( "Transaction already active" );
}
LOG.debug( "begin" );
this.transactionDriverControl.begin();
}
@Override
public void commit() {
if ( !isActive( true ) ) {
// allow MARKED_ROLLBACK to propagate through to transactionDriverControl
// the boolean passed to isActive indicates whether MARKED_ROLLBACK should be
// considered active
//
// essentially here we have a transaction that is not active and
// has not been marked for rollback only
throw new IllegalStateException( "Transaction not successfully started" );
}
LOG.debug( "committing" );
try {
internalGetTransactionDriverControl().commit();
}
catch (RuntimeException e) {
throw exceptionConverter.convertCommitException( e );
}
}
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
public void rollback() {
if ( !isActive() ) {
if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) {
throw new IllegalStateException(
"JPA compliance dictates throwing IllegalStateException when #rollback " +
"is called on non-active transaction"
);
}
}
TransactionStatus status = getStatus();
if ( status == TransactionStatus.ROLLED_BACK || status == TransactionStatus.NOT_ACTIVE ) {
// Allow rollback() calls on completed transactions, just no-op.
LOG.debug( "rollback() called on an inactive transaction" );
return;
}
if ( !status.canRollback() ) {
throw new TransactionException( "Cannot rollback transaction in current status [" + status.name() + "]" );
}
LOG.debug( "rolling back" );
if ( status != TransactionStatus.FAILED_COMMIT || allowFailedCommitToPhysicallyRollback() ) {
internalGetTransactionDriverControl().rollback();
}
}
@Override
public boolean isActive() {
// old behavior considered TransactionStatus#MARKED_ROLLBACK as active
// return isActive( jpaCompliance.isJpaTransactionComplianceEnabled() ? false : true );
return isActive( true );
}
@Override
public boolean isActive(boolean isMarkedForRollbackConsideredActive) {
if ( transactionDriverControl == null ) {
if ( session.isOpen() ) {
transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
}
else {
return false;
}
}
return transactionDriverControl.isActive( isMarkedForRollbackConsideredActive );
}
@Override
public TransactionStatus getStatus() {
if ( transactionDriverControl == null ) {
if ( session.isOpen() ) {
transactionDriverControl = transactionCoordinator.getTransactionDriverControl();
}
else {
return TransactionStatus.NOT_ACTIVE;
}
}
return transactionDriverControl.getStatus();
}
@Override
public void registerSynchronization(Synchronization synchronization) throws HibernateException {
this.transactionCoordinator.getLocalSynchronizations().registerSynchronization( synchronization );
}
@Override
public void setTimeout(int seconds) {
this.transactionCoordinator.setTimeOut( seconds );
}
@Override
public int getTimeout() {
return this.transactionCoordinator.getTimeOut();
}
@Override
public void markRollbackOnly() {
// this is the Hibernate-specific API, whereas #setRollbackOnly is the
// JPA-defined API. In our opinion it is much more user-friendly to
// always allow user/integration to indicate that the transaction
// should not be allowed to commit.
//
// However.. should only "do something" on an active transaction
if ( isActive() ) {
internalGetTransactionDriverControl().markRollbackOnly();
}
}
@Override
public void setRollbackOnly() {
if ( !isActive() ) {
// Since this is the JPA-defined one, we make sure the txn is active first
// so long as compliance (JpaCompliance) has not been defined to disable
// that check - making this active more like Hibernate's #markRollbackOnly
if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) {
throw new IllegalStateException(
"JPA compliance dictates throwing IllegalStateException when #setRollbackOnly " +
"is called on non-active transaction"
);
}
else {
LOG.debug( "#setRollbackOnly called on a not-active transaction" );
}
}
else {
markRollbackOnly();
}
}
@Override
public boolean getRollbackOnly() {
if ( !isActive() ) {
if ( jpaCompliance.isJpaTransactionComplianceEnabled() ) {
throw new IllegalStateException(
"JPA compliance dictates throwing IllegalStateException when #getRollbackOnly " +
"is called on non-active transaction"
);
}
}
return getStatus() == TransactionStatus.MARKED_ROLLBACK;
}
protected boolean allowFailedCommitToPhysicallyRollback() {
return false;
}
}