Skip to content

Commit 490c990

Browse files
committed
HHH-19336 - Proper implementation for JPA extended locking scope
HHH-19459 - LockScope, FollowOnLocking HHH-19501 - Session#lock w/ pessimistic locks for scopes HHH-19502 - Disallow SKIP_LOCKED with Session#lock HHH-19503 - Track a Dialect's level of support for locking joined tables
1 parent af2fc48 commit 490c990

File tree

3 files changed

+89
-46
lines changed

3 files changed

+89
-46
lines changed

hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Helper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public enum Table implements TableInformation {
112112
public String getTableName() {
113113
return switch ( this ) {
114114
case BOOKS -> "books";
115-
case PERSONS -> "the_persons";
115+
case PERSONS -> "persons";
116116
case PUBLISHER -> "publishers";
117117
case REPORTS, JOINED_REPORTER -> "reports";
118118
case BOOK_GENRES -> "book_genres";
@@ -124,8 +124,7 @@ public String getTableName() {
124124
public String getTableAlias() {
125125
return switch ( this ) {
126126
case BOOKS -> "b1_0";
127-
case PERSONS -> "t1_0";
128-
case PUBLISHER -> "p1_0";
127+
case PUBLISHER, PERSONS -> "p1_0";
129128
case REPORTS -> "r1_0";
130129
case BOOK_GENRES -> "g1_0";
131130
case BOOK_AUTHORS -> "a1_0";

hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/Person.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @author Steve Ebersole
1414
*/
1515
@Entity
16-
@Table(name = "the_persons")
16+
@Table(name = "persons")
1717
public class Person {
1818
@Id
1919
private Integer id;

hibernate-core/src/test/java/org/hibernate/orm/test/locking/options/ScopeTests.java

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.LockMode;
1010
import org.hibernate.LockOptions;
1111
import org.hibernate.Locking;
12+
import org.hibernate.Timeouts;
1213
import org.hibernate.dialect.Dialect;
1314
import org.hibernate.dialect.H2Dialect;
1415
import org.hibernate.dialect.HSQLDialect;
@@ -34,7 +35,6 @@
3435
import static org.assertj.core.api.Assertions.assertThat;
3536
import static org.hibernate.LockMode.PESSIMISTIC_WRITE;
3637
import static org.hibernate.orm.test.locking.options.Helper.Table.BOOKS;
37-
import static org.hibernate.orm.test.locking.options.Helper.Table.BOOK_AUTHORS;
3838
import static org.hibernate.orm.test.locking.options.Helper.Table.BOOK_GENRES;
3939
import static org.hibernate.orm.test.locking.options.Helper.Table.JOINED_REPORTER;
4040
import static org.hibernate.orm.test.locking.options.Helper.Table.PERSONS;
@@ -62,6 +62,8 @@ void dropTestData(SessionFactoryScope factoryScope) {
6262
factoryScope.dropData();
6363
}
6464

65+
// todo : generally, we do not lock collection tables - HHH-19513 plus maybe general problem with many-to-many tables
66+
6567
@Test
6668
void testFind(SessionFactoryScope factoryScope) {
6769
final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector();
@@ -72,28 +74,25 @@ void testFind(SessionFactoryScope factoryScope) {
7274
assertThat( Hibernate.isInitialized( theTalisman ) ).isTrue();
7375
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
7476
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
75-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
76-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
77-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
77+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
78+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
7879
} );
7980
}
8081

8182
@Test
8283
void testFindWithExtended(SessionFactoryScope factoryScope) {
8384
final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector();
8485

85-
// note that this is not strictly spec compliant as it says EXTENDED should extend
86-
// the locks to the `book_genres` table...
86+
// note that this is not strictly spec compliant as it says EXTENDED should extend the locks to the `book_genres` table...
8787
factoryScope.inTransaction( (session) -> {
8888
sqlCollector.clear();
8989
final Book theTalisman = session.find( Book.class, 3, PESSIMISTIC_WRITE, EXTENDED );
9090
assertThat( Hibernate.isInitialized( theTalisman ) ).isTrue();
9191
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
9292
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
93-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
93+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
9494
// For strict compliance, EXTENDED here should lock `book_genres` but we do not
95-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
96-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
95+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
9796
} );
9897
}
9998

@@ -109,9 +108,8 @@ void testFindWithExtendedJpaExpectation(SessionFactoryScope factoryScope) {
109108
// these 2 assertions would depend a bit on the approach and/or dialect
110109
// assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
111110
// Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), Helper.Table.BOOK_GENRES );
112-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
113-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), true );
114-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
111+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
112+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", true );
115113
} );
116114
}
117115

@@ -133,11 +131,23 @@ void testFindWithExtendedAndFetch(SessionFactoryScope factoryScope) {
133131
new EnabledFetchProfile("book-genres")
134132
);
135133
assertThat( Hibernate.isInitialized( theTalisman ) ).isTrue();
136-
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
137-
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS, BOOK_GENRES );
138-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
139-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), true );
140-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
134+
135+
if ( session.getDialect().supportsOuterJoinForUpdate() ) {
136+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
137+
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS, BOOK_GENRES );
138+
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
139+
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), true );
140+
}
141+
else {
142+
// should be 3, but follow-on locking is not locking collection tables...
143+
// todo : track this down - HHH-19513
144+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 2 );
145+
Helper.checkSql( sqlCollector.getSqlQueries().get( 1 ), session.getDialect(), BOOKS );
146+
147+
// todo : track this down - HHH-19513
148+
//Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), BOOK_GENRES );
149+
//TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", true );
150+
}
141151
} );
142152
}
143153

@@ -158,18 +168,17 @@ void testLock(SessionFactoryScope factoryScope) {
158168
//Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
159169
final String sql = sqlCollector.getSqlQueries().get( 0 );
160170
if ( session.getDialect().getPessimisticLockStyle() == PessimisticLockStyle.CLAUSE ) {
161-
assertThat( sql ).endsWith( " for update" );
171+
final String expectedClause = session.getDialect().getWriteLockString( Timeouts.WAIT_FOREVER );
172+
assertThat( sql ).endsWith( expectedClause );
162173
}
163174
else {
164175
final LockOptions lockOptions = new LockOptions( LockMode.PESSIMISTIC_WRITE );
165176
final String booksTableReference = session.getDialect().appendLockHint( lockOptions, BOOKS.getTableName() );
166177
assertThat( sql ).contains( booksTableReference );
167178
}
168179
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169-
170-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
171-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
172-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
180+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
181+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
173182
} );
174183
}
175184

@@ -185,9 +194,9 @@ void testLockWithExtended(SessionFactoryScope factoryScope) {
185194
session.lock( theTalisman, PESSIMISTIC_WRITE, EXTENDED );
186195
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
187196
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
197+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
188198
// Again, for strict compliance, EXTENDED here should lock `book_genres` but we do not
189-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
190-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
199+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
191200
} );
192201
}
193202

@@ -203,9 +212,8 @@ void testRefresh(SessionFactoryScope factoryScope) {
203212
session.refresh( theTalisman, PESSIMISTIC_WRITE );
204213
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
205214
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
206-
TransactionUtil.deleteFromTable( factoryScope, PERSONS.getTableName(), false );
207-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
208-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
215+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
216+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
209217
} );
210218
}
211219

@@ -221,13 +229,14 @@ void testRefreshWithExtended(SessionFactoryScope factoryScope) {
221229
session.lock( theTalisman, PESSIMISTIC_WRITE, EXTENDED );
222230
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
223231
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), BOOKS );
224-
TransactionUtil.deleteFromTable( factoryScope, BOOKS.getTableName(), true );
225-
TransactionUtil.deleteFromTable( factoryScope, BOOK_GENRES.getTableName(), false );
226-
TransactionUtil.deleteFromTable( factoryScope, BOOK_AUTHORS.getTableName(), false );
232+
TransactionUtil.updateTable( factoryScope, BOOKS.getTableName(), "title", true );
233+
// Again, for strict compliance, EXTENDED here should lock `book_genres` but we do not
234+
TransactionUtil.updateTable( factoryScope, BOOK_GENRES.getTableName(), "genre", false );
227235
} );
228236
}
229237

230238
@Test
239+
@SkipForDialect(dialectClass = HSQLDialect.class, reason = "See https://sourceforge.net/p/hsqldb/bugs/1734/")
231240
void testEagerFind(SessionFactoryScope factoryScope) {
232241
final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector();
233242

@@ -276,12 +285,29 @@ void testEagerFindWithExtended(SessionFactoryScope factoryScope) {
276285

277286
factoryScope.inTransaction( (session) -> {
278287
sqlCollector.clear();
279-
final Report report = session.find( Report.class, 2, PESSIMISTIC_WRITE, EXTENDED );
280-
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
281-
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS );
282-
TransactionUtil.deleteFromTable( factoryScope, REPORTS.getTableName(), true );
283-
TransactionUtil.deleteFromTable( factoryScope, PERSONS.getTableName(), false );
284-
TransactionUtil.deleteFromTable( factoryScope, REPORT_LABELS.getTableName(), true );
288+
session.find( Report.class, 2, PESSIMISTIC_WRITE, EXTENDED );
289+
if ( session.getDialect().supportsOuterJoinForUpdate() ) {
290+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
291+
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS );
292+
TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true );
293+
TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", false );
294+
TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true );
295+
}
296+
else {
297+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 3 );
298+
Helper.checkSql( sqlCollector.getSqlQueries().get( 1 ), session.getDialect(), REPORTS );
299+
Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), PERSONS );
300+
TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true );
301+
302+
// these should happen but currently do not - follow-on locking is not locking element-collection tables...
303+
// todo : track this down - HHH-19513
304+
//Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), REPORT_LABELS );
305+
//TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true );
306+
307+
// this one should not happen at all. follow-on locking is not understanding scope probably..
308+
// todo : track this down - HHH-19514
309+
TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true );
310+
}
285311
} );
286312
}
287313

@@ -293,12 +319,30 @@ void testEagerFindWithFetchScope(SessionFactoryScope factoryScope) {
293319

294320
factoryScope.inTransaction( (session) -> {
295321
sqlCollector.clear();
296-
final Report report = session.find( Report.class, 2, PESSIMISTIC_WRITE, Locking.Scope.INCLUDE_FETCHES );
297-
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
298-
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS, JOINED_REPORTER );
299-
TransactionUtil.deleteFromTable( factoryScope, REPORTS.getTableName(), true );
300-
TransactionUtil.deleteFromTable( factoryScope, PERSONS.getTableName(), true );
301-
TransactionUtil.deleteFromTable( factoryScope, REPORT_LABELS.getTableName(), true );
322+
session.find( Report.class, 2, PESSIMISTIC_WRITE, Locking.Scope.INCLUDE_FETCHES );
323+
324+
if ( session.getDialect().supportsOuterJoinForUpdate() ) {
325+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 );
326+
Helper.checkSql( sqlCollector.getSqlQueries().get( 0 ), session.getDialect(), REPORTS, REPORT_LABELS, JOINED_REPORTER );
327+
TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true );
328+
TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true );
329+
TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true );
330+
}
331+
else {
332+
assertThat( sqlCollector.getSqlQueries() ).hasSize( 3 );
333+
Helper.checkSql( sqlCollector.getSqlQueries().get( 1 ), session.getDialect(), REPORTS );
334+
Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), PERSONS );
335+
TransactionUtil.updateTable( factoryScope, REPORTS.getTableName(), "title", true );
336+
337+
// these should happen but currently do not - follow-on locking is not locking element-collection tables...
338+
// todo : track this down - HHH-19513
339+
//Helper.checkSql( sqlCollector.getSqlQueries().get( 2 ), session.getDialect(), REPORT_LABELS );
340+
//TransactionUtil.updateTable( factoryScope, REPORT_LABELS.getTableName(), "txt", true );
341+
342+
// this one should not happen at all. follow-on locking is not understanding scope probably..
343+
// todo : track this down - HHH-19514
344+
TransactionUtil.updateTable( factoryScope, PERSONS.getTableName(), "name", true );
345+
}
302346
} );
303347
}
304348
}

0 commit comments

Comments
 (0)