Skip to content

Commit 1622dd3

Browse files
committed
HHH-19602 - Adjust JdbcOperation to allow more-than-one statement
HHH-19513 - Follow-on locking does not lock element-collection tables HHH-19782 - Oracle support for locking across joins # Conflicts: # hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java # hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java
1 parent b77893f commit 1622dd3

File tree

120 files changed

+4706
-1156
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+4706
-1156
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/function/NumberSeriesGenerateSeriesFunction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
import org.hibernate.sql.ast.tree.predicate.PredicateContainer;
4949
import org.hibernate.sql.ast.tree.select.QuerySpec;
5050
import org.hibernate.sql.ast.tree.select.SelectStatement;
51-
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
51+
import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect;
5252
import org.hibernate.sql.results.internal.SqlSelectionImpl;
5353
import org.hibernate.type.BasicType;
5454
import org.hibernate.type.SqlTypes;

hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/Helper.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,15 @@
1515
import java.util.function.Function;
1616

1717
/**
18+
* Helper for dealing with {@linkplain Connection}-level lock timeouts.
19+
*
1820
* @author Steve Ebersole
1921
*/
2022
public class Helper {
23+
/**
24+
* Use the given {@code sql} statement to query the current lock-timeout for the
25+
* {@linkplain Connection} and use the {@code extractor} to process the value.
26+
*/
2127
public static Timeout getLockTimeout(
2228
String sql,
2329
TimeoutExtractor extractor,
@@ -37,15 +43,13 @@ public static Timeout getLockTimeout(
3743
}
3844
}
3945

46+
/**
47+
* Set the {@linkplain Connection}-level lock-timeout using the given {@code sql} command.
48+
*/
4049
public static void setLockTimeout(
41-
Timeout timeout,
42-
Function<Timeout,Integer> valueStrategy,
43-
String sqlFormat,
50+
String sql,
4451
Connection connection,
4552
SessionFactoryImplementor factory) {
46-
final int milliseconds = valueStrategy.apply( timeout );
47-
48-
final String sql = String.format( sqlFormat, milliseconds );
4953
try (final java.sql.Statement statement = connection.createStatement()) {
5054
factory.getJdbcServices().getSqlStatementLogger().logStatement( sql );
5155
statement.execute( sql );
@@ -56,6 +60,39 @@ public static void setLockTimeout(
5660
}
5761
}
5862

63+
/**
64+
* Set the {@linkplain Connection}-level lock-timeout using
65+
* the given {@code sqlFormat} (with a single format placeholder
66+
* for the {@code milliseconds} value).
67+
*
68+
* @see #setLockTimeout(String, Connection, SessionFactoryImplementor)
69+
*/
70+
public static void setLockTimeout(
71+
Integer milliseconds,
72+
String sqlFormat,
73+
Connection connection,
74+
SessionFactoryImplementor factory) {
75+
final String sql = String.format( sqlFormat, milliseconds );
76+
setLockTimeout( sql, connection, factory );
77+
}
78+
79+
/**
80+
* Set the {@linkplain Connection}-level lock-timeout. The passed
81+
* {@code valueStrategy} is used to interpret the {@code timeout}
82+
* which is then used with {@code sqlFormat} to execute the command.
83+
*
84+
* @see #setLockTimeout(Integer, String, Connection, SessionFactoryImplementor)
85+
*/
86+
public static void setLockTimeout(
87+
Timeout timeout,
88+
Function<Timeout,Integer> valueStrategy,
89+
String sqlFormat,
90+
Connection connection,
91+
SessionFactoryImplementor factory) {
92+
final int milliseconds = valueStrategy.apply( timeout );
93+
setLockTimeout( milliseconds, sqlFormat, connection, factory );
94+
}
95+
5996
@FunctionalInterface
6097
public interface TimeoutExtractor {
6198
Timeout extractFrom(ResultSet resultSet) throws SQLException;

hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/OracleLockingSupport.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ public RowLockStrategy getWriteRowLockStrategy() {
6161

6262
@Override
6363
public OuterJoinLockingType getOuterJoinLockingType() {
64-
return OuterJoinLockingType.UNSUPPORTED;
64+
// Per Loic, as of 23 at least, Oracle does support this.
65+
// Let's see what CI says for previous supported versions.
66+
return OuterJoinLockingType.IDENTIFIED;
6567
}
6668

6769
@Override

hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/SqlAstBasedLockingStrategy.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.hibernate.LockOptions;
99
import org.hibernate.Locking;
1010
import org.hibernate.StaleObjectStateException;
11+
import org.hibernate.collection.spi.PersistentCollection;
1112
import org.hibernate.dialect.lock.LockingStrategy;
1213
import org.hibernate.dialect.lock.LockingStrategyException;
1314
import org.hibernate.dialect.lock.PessimisticEntityLockException;
@@ -23,6 +24,7 @@
2324
import org.hibernate.metamodel.mapping.SelectableMapping;
2425
import org.hibernate.persister.entity.EntityPersister;
2526
import org.hibernate.query.sqm.ComparisonOperator;
27+
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
2628
import org.hibernate.spi.NavigablePath;
2729
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
2830
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
@@ -34,8 +36,9 @@
3436
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
3537
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
3638
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
37-
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
39+
import org.hibernate.sql.exec.internal.lock.LockingHelper;
3840
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
41+
import org.hibernate.sql.exec.spi.JdbcSelect;
3942
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
4043
import org.hibernate.sql.results.graph.basic.BasicResult;
4144
import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
@@ -157,7 +160,7 @@ public void lock(
157160
}
158161

159162
final SelectStatement selectStatement = new SelectStatement( rootQuerySpec, List.of( idResult ) );
160-
final JdbcOperationQuerySelect selectOperation = session
163+
final JdbcSelect selectOperation = session
161164
.getDialect()
162165
.getSqlAstTranslatorFactory()
163166
.buildSelectTranslator( factory, selectStatement )
@@ -176,6 +179,19 @@ public void lock(
176179
1,
177180
SingleResultConsumer.instance()
178181
);
182+
183+
if ( lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS ) {
184+
SqmMutationStrategyHelper.visitCollectionTables( entityToLock, (attribute) -> {
185+
final PersistentCollection<?> collectionToLock = (PersistentCollection<?>) attribute.getValue( object );
186+
LockingHelper.lockCollectionTable(
187+
attribute,
188+
lockMode,
189+
lockOptions.getTimeout(),
190+
collectionToLock,
191+
lockingExecutionContext
192+
);
193+
} );
194+
}
179195
}
180196
catch (LockTimeoutException e) {
181197
throw new PessimisticEntityLockException(

hibernate-core/src/main/java/org/hibernate/dialect/lock/internal/TransactSQLLockingSupport.java

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ public class TransactSQLLockingSupport extends LockingSupportParameterized {
4141
LockTimeoutType.NONE,
4242
RowLockStrategy.TABLE,
4343
OuterJoinLockingType.IDENTIFIED,
44-
ConnectionLockTimeoutStrategy.NONE
44+
SybaseImpl.IMPL
4545
);
4646

4747
public static final LockingSupport SYBASE_ASE = new TransactSQLLockingSupport(
48-
PessimisticLockStyle.NONE,
48+
PessimisticLockStyle.TABLE_HINT,
4949
LockTimeoutType.CONNECTION,
5050
LockTimeoutType.NONE,
5151
LockTimeoutType.NONE,
5252
RowLockStrategy.TABLE,
5353
OuterJoinLockingType.IDENTIFIED,
54-
ConnectionLockTimeoutStrategy.NONE
54+
SybaseImpl.IMPL
5555
);
5656

5757
public static final LockingSupport SYBASE_LEGACY = new TransactSQLLockingSupport(
@@ -61,7 +61,7 @@ public class TransactSQLLockingSupport extends LockingSupportParameterized {
6161
LockTimeoutType.NONE,
6262
RowLockStrategy.TABLE,
6363
OuterJoinLockingType.IDENTIFIED,
64-
ConnectionLockTimeoutStrategy.NONE
64+
SybaseImpl.IMPL
6565
);
6666

6767
public static LockingSupport forSybaseAnywhere(DatabaseVersion version) {
@@ -76,7 +76,7 @@ public static LockingSupport forSybaseAnywhere(DatabaseVersion version) {
7676
? RowLockStrategy.COLUMN
7777
: RowLockStrategy.TABLE,
7878
OuterJoinLockingType.IDENTIFIED,
79-
ConnectionLockTimeoutStrategy.NONE
79+
SybaseImpl.IMPL
8080
);
8181
}
8282

@@ -111,13 +111,13 @@ public static class SQLServerImpl implements ConnectionLockTimeoutStrategy {
111111

112112
@Override
113113
public Level getSupportedLevel() {
114-
return ConnectionLockTimeoutStrategy.Level.EXTENDED;
114+
return Level.EXTENDED;
115115
}
116116

117117
@Override
118118
public Timeout getLockTimeout(Connection connection, SessionFactoryImplementor factory) {
119119
return Helper.getLockTimeout(
120-
"select @@LOCK_TIMEOUT",
120+
"select @@lock_timeout",
121121
(resultSet) -> {
122122
final int timeoutInMilliseconds = resultSet.getInt( 1 );
123123
return switch ( timeoutInMilliseconds ) {
@@ -148,4 +148,56 @@ public void setLockTimeout(Timeout timeout, Connection connection, SessionFactor
148148
);
149149
}
150150
}
151+
152+
public static class SybaseImpl implements ConnectionLockTimeoutStrategy {
153+
public static final SybaseImpl IMPL = new SybaseImpl();
154+
155+
@Override
156+
public Level getSupportedLevel() {
157+
return Level.SUPPORTED;
158+
}
159+
160+
@Override
161+
public Timeout getLockTimeout(Connection connection, SessionFactoryImplementor factory) {
162+
return Helper.getLockTimeout(
163+
"select @@lock_timeout",
164+
(resultSet) -> {
165+
final int timeoutInMilliseconds = resultSet.getInt( 1 );
166+
return switch ( timeoutInMilliseconds ) {
167+
case -1 -> Timeouts.WAIT_FOREVER;
168+
case 0 -> Timeouts.NO_WAIT;
169+
default -> Timeout.milliseconds( timeoutInMilliseconds );
170+
};
171+
},
172+
connection,
173+
factory
174+
);
175+
}
176+
177+
@Override
178+
public void setLockTimeout(Timeout timeout, Connection connection, SessionFactoryImplementor factory) {
179+
final int milliseconds = timeout.milliseconds();
180+
181+
if ( milliseconds == Timeouts.SKIP_LOCKED_MILLI ) {
182+
throw new HibernateException( "Sybase does not accept skip-locked for lock-timeout" );
183+
}
184+
185+
// Sybase needs a special syntax for NO_WAIT rather than a number
186+
if ( milliseconds == Timeouts.NO_WAIT_MILLI ) {
187+
// NOTE: The docs say this is supported, and it does not fail when used,
188+
// but immediately after the setting value is still -1. So it seems to
189+
// allow the call but ignore it. Might just be jTDS.
190+
Helper.setLockTimeout( "set lock nowait", connection, factory );
191+
}
192+
else if ( milliseconds == Timeouts.WAIT_FOREVER_MILLI ) {
193+
// Even though Sybase's wait-forever (and default) value is -1, it won't accept
194+
// -1 as a value because, well, of course it won't. Need to set max value instead
195+
// because, well, of course you do.
196+
Helper.setLockTimeout( 2147483647, "set lock wait %s", connection, factory );
197+
}
198+
else {
199+
Helper.setLockTimeout( milliseconds, "set lock wait %s", connection, factory );
200+
}
201+
}
202+
}
151203
}

hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/OracleSqlAstTranslator.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
190190
}
191191
}
192192

193-
if ( strategy != LockStrategy.FOLLOW_ON && needsLockingWrapper( querySpec ) && !canApplyLockingWrapper( querySpec ) ) {
193+
if ( strategy != LockStrategy.FOLLOW_ON
194+
&& needsLockingWrapper( querySpec, followOnStrategy )
195+
&& !canApplyLockingWrapper( querySpec ) ) {
194196
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
195197
throw new IllegalQueryOperationException( "Locking with OFFSET/FETCH is not supported" );
196198
}
@@ -317,7 +319,11 @@ public void visitQueryGroup(QueryGroup queryGroup) {
317319

318320
@Override
319321
public void visitQuerySpec(QuerySpec querySpec) {
320-
final EntityIdentifierMapping identifierMappingForLockingWrapper = identifierMappingForLockingWrapper( querySpec );
322+
final LockOptions lockOptions = getLockOptions();
323+
final Locking.FollowOn followOnStrategy = lockOptions == null
324+
? Locking.FollowOn.ALLOW
325+
: lockOptions.getFollowOnStrategy();
326+
final EntityIdentifierMapping identifierMappingForLockingWrapper = identifierMappingForLockingWrapper( querySpec, followOnStrategy );
321327
final Expression offsetExpression;
322328
final Expression fetchExpression;
323329
final FetchClauseType fetchClauseType;
@@ -423,13 +429,13 @@ private QuerySpec createLockingWrapper(
423429
return lockingWrapper;
424430
}
425431

426-
private EntityIdentifierMapping identifierMappingForLockingWrapper(QuerySpec querySpec) {
432+
private EntityIdentifierMapping identifierMappingForLockingWrapper(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {
427433
// We only need a locking wrapper for very simple queries
428434
if ( canApplyLockingWrapper( querySpec )
429435
// There must be the need for locking in this query
430436
&& needsLocking( querySpec )
431437
// The query uses some sort of pagination which makes the wrapper necessary
432-
&& needsLockingWrapper( querySpec )
438+
&& needsLockingWrapper( querySpec, followOnStrategy )
433439
// The query may not have a group by, having and distinct clause, or use aggregate functions,
434440
// as these features will force the use of follow-on locking
435441
&& querySpec.getGroupByClauseExpressions().isEmpty()
@@ -450,9 +456,8 @@ private boolean canApplyLockingWrapper(QuerySpec querySpec) {
450456
&& fromClause.getRoots().get( 0 ).getModelPart() instanceof EntityMappingType;
451457
}
452458

453-
private boolean needsLockingWrapper(QuerySpec querySpec) {
454-
final LockOptions lockOptions = getLockOptions();
455-
if ( lockOptions.getFollowOnStrategy() == Locking.FollowOn.FORCE ) {
459+
private boolean needsLockingWrapper(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {
460+
if ( followOnStrategy == Locking.FollowOn.FORCE ) {
456461
// user explicitly asked for follow-on locking
457462
return false;
458463
}

hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SybaseASESqlAstTranslator.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,6 @@ public static String determineLockHint(LockMode lockMode, int effectiveLockTimeo
229229
protected LockStrategy determineLockingStrategy(
230230
QuerySpec querySpec,
231231
Locking.FollowOn followOnStrategy) {
232-
if ( followOnStrategy == Locking.FollowOn.FORCE ) {
233-
return LockStrategy.FOLLOW_ON;
234-
}
235232
// No need for follow on locking
236233
return LockStrategy.CLAUSE;
237234
}

hibernate-core/src/main/java/org/hibernate/dialect/sql/ast/SybaseSqlAstTranslator.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,6 @@ public static String determineLockHint(LockMode lockMode) {
187187
protected LockStrategy determineLockingStrategy(
188188
QuerySpec querySpec,
189189
Locking.FollowOn followOnStrategy) {
190-
if ( followOnStrategy == Locking.FollowOn.FORCE ) {
191-
return LockStrategy.FOLLOW_ON;
192-
}
193190
// No need for follow on locking
194191
return LockStrategy.CLAUSE;
195192
}

hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/JdbcServices.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import org.hibernate.sql.exec.internal.StandardJdbcMutationExecutor;
1818
import org.hibernate.sql.exec.spi.JdbcMutationExecutor;
1919
import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
20-
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
20+
import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect;
2121
import org.hibernate.sql.exec.spi.JdbcSelectExecutor;
2222

2323
/**

0 commit comments

Comments
 (0)