HHH-10619 Add test for JTA timeout handling
This commit is contained in:
parent
ed9d7c60a6
commit
76ca5fb790
|
@ -14,6 +14,7 @@ import java.util.Map;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.QueryTimeoutException;
|
||||
import org.hibernate.boot.model.TypeContributions;
|
||||
import org.hibernate.dialect.DatabaseVersion;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
@ -654,6 +655,8 @@ public class SybaseASELegacyDialect extends SybaseLegacyDialect {
|
|||
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
|
||||
if ( sqlState != null ) {
|
||||
switch ( sqlState ) {
|
||||
case "HY008":
|
||||
return new QueryTimeoutException( message, sqlException, sql );
|
||||
case "JZ0TO":
|
||||
case "JZ006":
|
||||
return new LockTimeoutException( message, sqlException, sql );
|
||||
|
|
|
@ -236,6 +236,8 @@ task generateEnversStaticMetamodel(
|
|||
|
||||
tasks.withType( Test.class ).each { test ->
|
||||
test.systemProperty 'file.encoding', 'utf-8'
|
||||
// Allow creating a function in HSQLDB for this Java method
|
||||
test.systemProperty 'hsqldb.method_class_names', 'org.hibernate.orm.test.jpa.transaction.TransactionTimeoutTest.sleep'
|
||||
|
||||
// See org.hibernate.boot.model.naming.NamingHelperTest.DefaultCharset.set
|
||||
test.jvmArgs( ['--add-opens', 'java.base/java.nio.charset=ALL-UNNAMED'] )
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.sql.Types;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.QueryTimeoutException;
|
||||
import org.hibernate.boot.model.TypeContributions;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
import org.hibernate.dialect.pagination.TopLimitHandler;
|
||||
|
@ -655,6 +656,8 @@ public class SybaseASEDialect extends SybaseDialect {
|
|||
final int errorCode = JdbcExceptionHelper.extractErrorCode( sqlException );
|
||||
if ( sqlState != null ) {
|
||||
switch ( sqlState ) {
|
||||
case "HY008":
|
||||
return new QueryTimeoutException( message, sqlException, sql );
|
||||
case "JZ0TO":
|
||||
case "JZ006":
|
||||
return new LockTimeoutException( message, sqlException, sql );
|
||||
|
|
|
@ -404,28 +404,31 @@ public class SessionImpl
|
|||
|
||||
// todo : we want this check if usage is JPA, but not native Hibernate usage
|
||||
final SessionFactoryImplementor sessionFactory = getSessionFactory();
|
||||
if ( sessionFactory.getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
// Original hibernate-entitymanager EM#close behavior
|
||||
checkSessionFactoryOpen();
|
||||
checkOpenOrWaitingForAutoClose();
|
||||
if ( fastSessionServices.discardOnClose || !isTransactionInProgressAndNotMarkedForRollback() ) {
|
||||
super.close();
|
||||
try {
|
||||
if ( sessionFactory.getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
// Original hibernate-entitymanager EM#close behavior
|
||||
checkSessionFactoryOpen();
|
||||
checkOpenOrWaitingForAutoClose();
|
||||
if ( fastSessionServices.discardOnClose || !isTransactionInProgressAndNotMarkedForRollback() ) {
|
||||
super.close();
|
||||
}
|
||||
else {
|
||||
//Otherwise, session auto-close will be enabled by shouldAutoCloseSession().
|
||||
prepareForAutoClose();
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Otherwise, session auto-close will be enabled by shouldAutoCloseSession().
|
||||
prepareForAutoClose();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
super.close();
|
||||
}
|
||||
finally {
|
||||
final StatisticsImplementor statistics = sessionFactory.getStatistics();
|
||||
if ( statistics.isStatisticsEnabled() ) {
|
||||
statistics.closeSession();
|
||||
}
|
||||
|
||||
final StatisticsImplementor statistics = sessionFactory.getStatistics();
|
||||
if ( statistics.isStatisticsEnabled() ) {
|
||||
statistics.closeSession();
|
||||
eventManager.completeSessionClosedEvent( sessionClosedEvent, this );
|
||||
}
|
||||
|
||||
eventManager.completeSessionClosedEvent( sessionClosedEvent, this );
|
||||
}
|
||||
|
||||
private boolean isTransactionInProgressAndNotMarkedForRollback() {
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* 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.orm.test.jpa.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.dialect.AbstractTransactSQLDialect;
|
||||
import org.hibernate.dialect.CockroachDialect;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.DerbyDialect;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
|
||||
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.hibernate.testing.orm.junit.SettingProvider;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.ThrowingConsumer;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import jakarta.persistence.LockTimeoutException;
|
||||
import jakarta.persistence.ParameterMode;
|
||||
import jakarta.persistence.QueryTimeoutException;
|
||||
import jakarta.transaction.Status;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Jpa(
|
||||
integrationSettings = {
|
||||
@Setting(name = org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, value = "org.hibernate.testing.jta.JtaAwareConnectionProviderImpl"),
|
||||
@Setting(name = org.hibernate.cfg.AvailableSettings.JPA_TRANSACTION_TYPE, value = "JTA")
|
||||
},
|
||||
settingProviders = {
|
||||
@SettingProvider(
|
||||
settingName = AvailableSettings.JTA_PLATFORM,
|
||||
provider = JtaPlatformSettingProvider.class
|
||||
)
|
||||
})
|
||||
@JiraKey("HHH-10619")
|
||||
public class TransactionTimeoutTest {
|
||||
|
||||
@Test
|
||||
@RequiresDialect(H2Dialect.class)
|
||||
public void testH2(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "create alias sleep for \"" + TransactionTimeoutTest.class.getName() + ".sleep\"" )
|
||||
.executeUpdate();
|
||||
entityManager.createNativeQuery( "select sleep(10000)", Long.class ).getResultList();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(HSQLDialect.class)
|
||||
public void testHSQL(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "create function sleep(secs bigint) returns bigint no sql language java parameter style java deterministic external name 'CLASSPATH:" + TransactionTimeoutTest.class.getName() + ".sleep'" )
|
||||
.executeUpdate();
|
||||
entityManager.createNativeQuery( "select sleep(10000) from (values (0)) d" ).getResultList();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(DerbyDialect.class)
|
||||
public void testDerby(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "create procedure sleep(in secs bigint, out slept bigint) no sql language java parameter style java deterministic external name '" + TransactionTimeoutTest.class.getName() + ".sleep'" )
|
||||
.executeUpdate();
|
||||
entityManager.createStoredProcedureQuery( "sleep" )
|
||||
.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN )
|
||||
.registerStoredProcedureParameter( 2, Long.class, ParameterMode.OUT )
|
||||
.setParameter( 1, 10_000L )
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(PostgreSQLDialect.class)
|
||||
@RequiresDialect(CockroachDialect.class)
|
||||
public void testPostgreSQL(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "select pg_sleep(10)" ).getResultList();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(MySQLDialect.class)
|
||||
public void testMySQL(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "select sleep(10)" ).getResultList();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(OracleDialect.class)
|
||||
public void testOracle(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createStoredProcedureQuery( "DBMS_SESSION.SLEEP" )
|
||||
.registerStoredProcedureParameter( 1, BigDecimal.class, ParameterMode.IN )
|
||||
.setParameter( 1, BigDecimal.valueOf( 10L ) )
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(AbstractTransactSQLDialect.class)
|
||||
public void testTSQL(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createNativeQuery( "waitfor delay '00:00:10'" ).getResultList();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialect(DB2Dialect.class)
|
||||
public void testDB2(EntityManagerFactoryScope scope) throws Throwable {
|
||||
test( scope, entityManager -> {
|
||||
entityManager.createStoredProcedureQuery( "DBMS_LOCK.SLEEP" )
|
||||
.registerStoredProcedureParameter( 1, BigDecimal.class, ParameterMode.IN )
|
||||
.setParameter( 1, BigDecimal.valueOf( 10L ) )
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
private void test(EntityManagerFactoryScope scope, ThrowingConsumer<EntityManager> consumer) throws Throwable {
|
||||
TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager();
|
||||
EntityManagerFactory entityManagerFactory = scope.getEntityManagerFactory();
|
||||
|
||||
transactionManager.setTransactionTimeout( 2 );
|
||||
transactionManager.begin();
|
||||
|
||||
try (EntityManager entityManager = entityManagerFactory.createEntityManager()) {
|
||||
entityManager.joinTransaction();
|
||||
consumer.accept( entityManager );
|
||||
}
|
||||
catch (QueryTimeoutException | LockTimeoutException ex) {
|
||||
// This is fine
|
||||
}
|
||||
catch (HibernateException ex) {
|
||||
assertThat( ex.getMessage() ).isEqualTo( "Transaction was rolled back in a different thread" );
|
||||
}
|
||||
finally {
|
||||
assertThat( transactionManager.getStatus() ).isIn( Status.STATUS_ROLLEDBACK, Status.STATUS_ROLLING_BACK, Status.STATUS_MARKED_ROLLBACK );
|
||||
transactionManager.rollback();
|
||||
}
|
||||
}
|
||||
|
||||
public static long sleep(long millis) throws InterruptedException {
|
||||
long start = System.currentTimeMillis();
|
||||
Thread.sleep( millis );
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
public static void sleep(long millis, long[] slept) throws InterruptedException {
|
||||
slept[0] = sleep(millis);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue