HHH-10619 Add test for JTA timeout handling

This commit is contained in:
Christian Beikov 2024-01-30 17:02:22 +01:00
parent ed9d7c60a6
commit 76ca5fb790
6 changed files with 3002 additions and 36 deletions

View File

@ -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 );

View File

@ -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'] )

View File

@ -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 );

View File

@ -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() {

View File

@ -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);
}
}