diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java index c9da4655af7b..2e55033c5f5b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/jpa/FollowOnLockingTest.java @@ -100,7 +100,7 @@ public void testQueryLocking(SessionFactoryScope scope, boolean followOnLocking) statementInspector.assertExecutedCount( 1 ); } - TransactionUtil.updateTable( scope, "employees", "salary", true ); + TransactionUtil.assertRowLock( scope, "employees", "salary", "id", employees.get( 0 ).getId(), true ); } ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ConnectionLockTimeoutTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ConnectionLockTimeoutTests.java index db8130f37d3b..82b13b243d63 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ConnectionLockTimeoutTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ConnectionLockTimeoutTests.java @@ -44,16 +44,21 @@ void testSimpleUsage(SessionFactoryScope factoryScope) { final Timeout initialLockTimeout = connectionStrategy.getLockTimeout( conn, session.getFactory() ); assertThat( initialLockTimeout.milliseconds() ).isEqualTo( expectedInitialValue ); - final Timeout timeout = Timeout.milliseconds( 2000 ); - connectionStrategy.setLockTimeout( timeout, conn, session.getFactory() ); + try { + final Timeout timeout = Timeout.milliseconds( 2000 ); + connectionStrategy.setLockTimeout( timeout, conn, session.getFactory() ); - final Timeout adjustedLockTimeout = connectionStrategy.getLockTimeout( conn, session.getFactory() ); - assertThat( adjustedLockTimeout.milliseconds() ).isEqualTo( 2000 ); + final Timeout adjustedLockTimeout = connectionStrategy.getLockTimeout( conn, session.getFactory() ); + assertThat( adjustedLockTimeout.milliseconds() ).isEqualTo( 2000 ); - connectionStrategy.setLockTimeout( Timeouts.WAIT_FOREVER, conn, session.getFactory() ); + connectionStrategy.setLockTimeout( Timeouts.WAIT_FOREVER, conn, session.getFactory() ); - final Timeout resetLockTimeout = connectionStrategy.getLockTimeout( conn, session.getFactory() ); - assertThat( resetLockTimeout.milliseconds() ).isEqualTo( Timeouts.WAIT_FOREVER_MILLI ); + final Timeout resetLockTimeout = connectionStrategy.getLockTimeout( conn, session.getFactory() ); + assertThat( resetLockTimeout.milliseconds() ).isEqualTo( Timeouts.WAIT_FOREVER_MILLI ); + } + finally { + connectionStrategy.setLockTimeout( Timeout.milliseconds( expectedInitialValue ), conn, session.getFactory() ); + } } ) ); } @@ -85,6 +90,13 @@ void testNoWait(SessionFactoryScope factoryScope) { final ConnectionLockTimeoutStrategy connectionStrategy = lockingSupport.getConnectionLockTimeoutStrategy(); final ConnectionLockTimeoutStrategy.Level lockTimeoutType = connectionStrategy.getSupportedLevel(); + final int initialValue; + if ( session.getDialect() instanceof MySQLDialect ) { + initialValue = 50; + } + else { + initialValue = Timeouts.WAIT_FOREVER_MILLI; + } try { connectionStrategy.setLockTimeout( Timeouts.NO_WAIT, conn, session.getFactory() ); @@ -101,6 +113,9 @@ void testNoWait(SessionFactoryScope factoryScope) { throw e; } } + finally { + connectionStrategy.setLockTimeout( Timeout.milliseconds( initialValue ), conn, session.getFactory() ); + } } ) ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Report.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Report.java index 992e4017d57a..26696edf7175 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Report.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Report.java @@ -52,4 +52,44 @@ public Report(Integer id, Person reporter, String... labels) { this.reporter = reporter; this.labels = Helper.toSet( labels ); } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getRevision() { + return revision; + } + + public void setRevision(Integer revision) { + this.revision = revision; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Person getReporter() { + return reporter; + } + + public void setReporter(Person reporter) { + this.reporter = reporter; + } + + public Set getLabels() { + return labels; + } + + public void setLabels(Set labels) { + this.labels = labels; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeAndSecondaryTableTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeAndSecondaryTableTests.java index b27a8d56b73f..fe983fe804b5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeAndSecondaryTableTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeAndSecondaryTableTests.java @@ -48,11 +48,11 @@ void simpleTest(SessionFactoryScope factoryScope) { session.clear(); sqlCollector.clear(); - session.find( Detail.class, 1, LockModeType.PESSIMISTIC_WRITE ); + final Detail detail = session.find( Detail.class, 1, LockModeType.PESSIMISTIC_WRITE ); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), Tables.DETAILS, Tables.SUPPLEMENTALS ); - TransactionUtil.updateTable( factoryScope, Tables.DETAILS.getTableName(), "name", true ); - TransactionUtil.updateTable( factoryScope, Tables.SUPPLEMENTALS.getTableName(), "txt", true ); + TransactionUtil.assertRowLock( factoryScope, Tables.DETAILS.getTableName(), "name", "id", detail.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, Tables.SUPPLEMENTALS.getTableName(), "txt", "detail_fk", detail.getId(), true ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeTests.java index e460c7ffac88..44bebf17a10e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeTests.java @@ -69,8 +69,8 @@ void testFind(SessionFactoryScope factoryScope) { assertThat( Hibernate.isInitialized( theTalisman ) ).isTrue(); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -85,9 +85,9 @@ void testFindWithExtended(SessionFactoryScope factoryScope) { assertThat( Hibernate.isInitialized( theTalisman ) ).isTrue(); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); // For strict compliance, EXTENDED here should lock `book_genres` but we do not - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -103,8 +103,8 @@ void testFindWithExtendedJpaExpectation(SessionFactoryScope factoryScope) { // these 2 assertions would depend a bit on the approach and/or dialect // assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); // Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), Helper.Table.BOOK_GENRES ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", true ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), true ); } ); } @@ -131,8 +131,8 @@ void testFindWithExtendedAndFetch(SessionFactoryScope factoryScope) { assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS, BOOK_GENRES ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", true ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), true ); } else { // should be 3, but follow-on locking is not locking collection tables... @@ -142,7 +142,7 @@ void testFindWithExtendedAndFetch(SessionFactoryScope factoryScope) { // todo : track this down - HHH-19513 //Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), BOOK_GENRES ); - //TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", true ); + //TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), true ); } } ); } @@ -161,8 +161,8 @@ void testLock(SessionFactoryScope factoryScope) { Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -178,9 +178,9 @@ void testLockWithExtended(SessionFactoryScope factoryScope) { session.lock( theTalisman, PESSIMISTIC_WRITE, EXTENDED ); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); // Again, for strict compliance, EXTENDED here should lock `book_genres` but we do not - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -196,8 +196,8 @@ void testRefresh(SessionFactoryScope factoryScope) { session.refresh( theTalisman, PESSIMISTIC_WRITE ); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -213,9 +213,9 @@ void testRefreshWithExtended(SessionFactoryScope factoryScope) { session.refresh( theTalisman, PESSIMISTIC_WRITE, EXTENDED ); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS ); - TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true ); + TransactionUtil.assertRowLock( factoryScope, BOOKS.getTableName(), "title", "id", theTalisman.getId(), true ); // Again, for strict compliance, EXTENDED here should lock `book_genres` but we do not - TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false ); + TransactionUtil.assertRowLock( factoryScope, BOOK_GENRES.getTableName(), "genre", "book_fk", theTalisman.getId(), false ); } ); } @@ -226,12 +226,12 @@ void testEagerFind(SessionFactoryScope factoryScope) { factoryScope.inTransaction( (session) -> { sqlCollector.clear(); - session.find( Report.class, 2, PESSIMISTIC_WRITE ); + final Report report = session.find( Report.class, 2, PESSIMISTIC_WRITE ); assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS ); - TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", willAggressivelyLockJoinedTables( session.getDialect() ) ); - TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", willAggressivelyLockJoinedTables( session.getDialect() ) ); + TransactionUtil.assertRowLock( factoryScope, REPORTS.getTableName(), "title", "id", report.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, REPORT_LABELS.getTableName(), "txt", "report_fk", report.getId(), willAggressivelyLockJoinedTables( session.getDialect() ) ); + TransactionUtil.assertRowLock( factoryScope, PERSONS.getTableName(), "name", "id", report.getReporter().getId(), willAggressivelyLockJoinedTables( session.getDialect() ) ); } ); } @@ -258,28 +258,29 @@ void testEagerFindWithExtended(SessionFactoryScope factoryScope) { factoryScope.inTransaction( (session) -> { sqlCollector.clear(); - session.find( Report.class, 2, PESSIMISTIC_WRITE, EXTENDED ); + final Report report = session.find( Report.class, 2, PESSIMISTIC_WRITE, EXTENDED ); if ( session.getDialect().supportsOuterJoinForUpdate() ) { assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS ); - TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", willAggressivelyLockJoinedTables( session.getDialect() ) ); - TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true ); + TransactionUtil.assertRowLock( factoryScope, REPORTS.getTableName(), "title", "id", report.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, PERSONS.getTableName(), "name", "id", report.getReporter().getId(), + willAggressivelyLockJoinedTables( session.getDialect() ) ); + TransactionUtil.assertRowLock( factoryScope, REPORT_LABELS.getTableName(), "txt", "report_fk", report.getId(), true ); } else { assertThat( sqlCollector.getSqlQueries() ).hasSize( 3 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 1 ), session.getDialect(), REPORTS ); Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), PERSONS ); - TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true ); + TransactionUtil.assertRowLock( factoryScope, REPORTS.getTableName(), "title", "id", report.getId(), true ); // these should happen but currently do not - follow-on locking is not locking element-collection tables... // todo : track this down - HHH-19513 //Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), REPORT_LABELS ); - //TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true ); + //TransactionUtil.assertRowLock( factoryScope, REPORT_LABELS.getTableName(), "txt", "report_fk", report.getId(), true ); // this one should not happen at all. follow-on locking is not understanding scope probably.. // todo : track this down - HHH-19514 - TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true ); + TransactionUtil.assertRowLock( factoryScope, PERSONS.getTableName(), "name", "id", report.getReporter().getId(), true ); } } ); } @@ -292,29 +293,29 @@ void testEagerFindWithFetchScope(SessionFactoryScope factoryScope) { factoryScope.inTransaction( (session) -> { sqlCollector.clear(); - session.find( Report.class, 2, PESSIMISTIC_WRITE, Locking.Scope.INCLUDE_FETCHES ); + final Report report = session.find( Report.class, 2, PESSIMISTIC_WRITE, Locking.Scope.INCLUDE_FETCHES ); if ( session.getDialect().supportsOuterJoinForUpdate() ) { assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS, JOINED_REPORTER ); - TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true ); - TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true ); - TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true ); + TransactionUtil.assertRowLock( factoryScope, REPORTS.getTableName(), "title", "id", report.getId(), true ); + TransactionUtil.assertRowLock( factoryScope, PERSONS.getTableName(), "name", "id", report.getReporter().getId(), true ); + TransactionUtil.assertRowLock( factoryScope, REPORT_LABELS.getTableName(), "txt", "report_fk", report.getId(), true ); } else { assertThat( sqlCollector.getSqlQueries() ).hasSize( 3 ); Helper.checkSql( sqlCollector.getSqlQueries().get( 1 ), session.getDialect(), REPORTS ); Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), PERSONS ); - TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true ); + TransactionUtil.assertRowLock( factoryScope, REPORTS.getTableName(), "title", "id", report.getId(), true ); // these should happen but currently do not - follow-on locking is not locking element-collection tables... // todo : track this down - HHH-19513 //Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), REPORT_LABELS ); - //TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true ); + //TransactionUtil.assertRowLock( factoryScope, REPORT_LABELS.getTableName(), "txt", "report_fk", report.getId(), true ); // this one should not happen at all. follow-on locking is not understanding scope probably.. // todo : track this down - HHH-19514 - TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true ); + TransactionUtil.assertRowLock( factoryScope, PERSONS.getTableName(), "name", "id", report.getReporter().getId(), true ); } } ); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/AsyncExecutor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/AsyncExecutor.java index 6f3ad73ec79f..07449caee2ac 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/AsyncExecutor.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/AsyncExecutor.java @@ -7,34 +7,42 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * @author Steve Ebersole */ public class AsyncExecutor { + + // Need more than a single thread, because not all databases support cancellation of statements waiting for locks + private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); + public static void executeAsync(Runnable action) { - final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final Future future = EXECUTOR_SERVICE.submit( action ); try { - executorService.submit( action ).get(); + future.get(); } catch (InterruptedException e) { - throw new RuntimeException( "Thread interruption", e ); + future.cancel( true ); + throw new TimeoutException( "Thread interruption", e ); } catch (ExecutionException e) { - throw new RuntimeException( "Async execution error", e ); + throw new RuntimeException( "Async execution error", e.getCause() ); } } public static void executeAsync(int timeout, TimeUnit timeoutUnit, Runnable action) { - final ExecutorService executorService = Executors.newSingleThreadExecutor(); + final Future future = EXECUTOR_SERVICE.submit( action ); try { - executorService.submit( action ).get( timeout, timeoutUnit ); + future.get( timeout, timeoutUnit ); } catch (InterruptedException e) { + future.cancel( true ); throw new TimeoutException( "Thread interruption", e ); } catch (java.util.concurrent.TimeoutException e) { + future.cancel( true ); throw new TimeoutException( "Thread timeout exceeded", e ); } catch (ExecutionException e) { diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java index 2ea5ce4aee3d..1f359c72243f 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/transaction/TransactionUtil.java @@ -4,14 +4,19 @@ */ package org.hibernate.testing.orm.transaction; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; import jakarta.persistence.EntityManager; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.SharedSessionContract; import org.hibernate.StatelessSession; import org.hibernate.Transaction; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.exception.ConstraintViolationException; @@ -147,36 +152,58 @@ private static R wrapInTransaction(SharedSessionContract session, T actio } } - public static void updateTable(SessionFactoryScope factoryScope, String tableName, String columnName, boolean expectingToBlock) { - try { - AsyncExecutor.executeAsync( 2, TimeUnit.SECONDS, () -> { - factoryScope.inTransaction( (session) -> { - //noinspection deprecation - final String sql = String.format( "update %s set %s = null", tableName, columnName ); - session.createNativeQuery( sql ).executeUpdate(); - if ( expectingToBlock ) { - fail( "Expecting update to " + tableName + " to block dues to locks" ); - } - } ); + public static void assertRowLock(SessionFactoryScope factoryScope, String tableName, String columnName, String idColumn, Number id, boolean expectingToBlock) { + final Dialect dialect = factoryScope.getSessionFactory().getJdbcServices().getDialect(); + final boolean skipLocked = dialect.getLockingSupport().getMetadata().supportsSkipLocked(); + // SQL Server readpast hint doesn't really work unfortunately + if ( skipLocked && !( dialect instanceof SQLServerDialect ) ) { + factoryScope.inTransaction( (session) -> { + final String baseSql = String.format( "select %s from %s t where %s=%s", columnName, tableName, idColumn, id ); + final String sql = dialect.applyLocksToSql( + baseSql, + new LockOptions( LockMode.UPGRADE_SKIPLOCKED ), + Map.of( "t", new String[0] ) + ); + final int resultSize = session.createNativeQuery( sql ).getResultList().size(); + if ( expectingToBlock && resultSize > 0 ) { + fail( "Expecting update to " + tableName + " to block dues to locks" ); + } + else if ( !expectingToBlock && resultSize == 0 ) { + fail( "Unexpected lock found on " + tableName ); + } } ); } - catch (AsyncExecutor.TimeoutException expected) { - if ( !expectingToBlock ) { - fail( "Expecting update to " + tableName + " to succeed, but failed due to async timeout (presumably due to locks)", expected ); + else { + try { + AsyncExecutor.executeAsync( 2, TimeUnit.SECONDS, () -> { + factoryScope.inTransaction( (session) -> { + //noinspection deprecation + final String sql = String.format( "update %s set %s = null", tableName, columnName ); + session.createNativeQuery( sql ).executeUpdate(); + if ( expectingToBlock ) { + fail( "Expecting update to " + tableName + " to block dues to locks" ); + } + } ); + } ); } - } - catch (RuntimeException re) { - if ( re.getCause() instanceof jakarta.persistence.LockTimeoutException - || re.getCause() instanceof org.hibernate.exception.LockTimeoutException ) { + catch (AsyncExecutor.TimeoutException expected) { if ( !expectingToBlock ) { - fail( "Expecting update to " + tableName + " to succeed, but failed due to async timeout (presumably due to locks)", re.getCause() ); + fail( "Expecting update to " + tableName + " to succeed, but failed due to async timeout (presumably due to locks)", expected ); } } - else if ( re.getCause() instanceof ConstraintViolationException cve ) { - throw cve; - } - else { - throw re; + catch (RuntimeException re) { + if ( re.getCause() instanceof jakarta.persistence.LockTimeoutException + || re.getCause() instanceof org.hibernate.exception.LockTimeoutException ) { + if ( !expectingToBlock ) { + fail( "Expecting update to " + tableName + " to succeed, but failed due to async timeout (presumably due to locks)", re.getCause() ); + } + } + else if ( re.getCause() instanceof ConstraintViolationException cve ) { + throw cve; + } + else { + throw re; + } } } }