Skip to content

Commit 8b4d781

Browse files
committed
feat: Add option to ignore FOR UPDATE / FOR SHARE lock hints
1 parent e599d0e commit 8b4d781

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

hibernate-dialect-v7/src/main/java/tech/ydb/hibernate/dialect/YdbDialect.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.hibernate.boot.model.FunctionContributions;
1010
import org.hibernate.boot.model.TypeContributions;
1111
import org.hibernate.dialect.Dialect;
12+
import org.hibernate.internal.util.config.ConfigurationHelper;
1213
import org.hibernate.dialect.identity.IdentityColumnSupport;
1314
import org.hibernate.dialect.pagination.LimitHandler;
1415
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
@@ -94,8 +95,20 @@ public class YdbDialect extends Dialect {
9495
);
9596
private static final ConcurrentHashMap<Integer, DecimalJdbcType> DECIMAL_JDBC_TYPE_CACHE = new ConcurrentHashMap<>();
9697

98+
/**
99+
* When true, FOR UPDATE / FOR SHARE lock hints are ignored and an empty string is returned.
100+
* Configured via {@value tech.ydb.hibernate.dialect.YdbSettings#IGNORE_LOCK_HINTS}.
101+
* Default is {@code false} to support backward compatibility
102+
*/
103+
private final boolean ignoreLockHints;
104+
97105
public YdbDialect(DialectResolutionInfo dialectResolutionInfo) {
98106
super(dialectResolutionInfo);
107+
ignoreLockHints = ConfigurationHelper.getBoolean(
108+
YdbSettings.IGNORE_LOCK_HINTS,
109+
dialectResolutionInfo.getConfigurationValues(),
110+
false
111+
);
99112
}
100113

101114
@Override
@@ -392,6 +405,9 @@ public String getCurrentTimestampSelectString() {
392405

393406
@Override
394407
public String getForUpdateString() {
408+
if (ignoreLockHints) {
409+
return "";
410+
}
395411
throw new UnsupportedOperationException("YDB does not support FOR UPDATE statement");
396412
}
397413

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package tech.ydb.hibernate.dialect;
2+
3+
/**
4+
* Dialect-specific configuration settings for YDB.
5+
*
6+
* @author Ainur Mukhtarov
7+
*/
8+
public interface YdbSettings {
9+
10+
/**
11+
* Specifies whether the dialect should ignore FOR UPDATE / FOR SHARE lock hints
12+
* and omit them from generated SQL.
13+
* YDB does not support {@code SELECT ... FOR UPDATE} or {@code FOR SHARE}.
14+
* When this setting is {@code true}, the dialect returns an empty string for
15+
* lock clauses, allowing applications that use
16+
* {@code PESSIMISTIC_WRITE} to run without errors.
17+
* Set to {@code false} to throw {@link UnsupportedOperationException} when
18+
* lock hints are requested, making the lack of support explicit.
19+
*
20+
* @settingDefault {@code false}
21+
*
22+
* @see jakarta.persistence.LockModeType#PESSIMISTIC_WRITE
23+
* @see jakarta.persistence.LockModeType#PESSIMISTIC_READ
24+
*/
25+
String IGNORE_LOCK_HINTS = "hibernate.dialect.ydb.ignore_lock_hints";
26+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package tech.ydb.hibernate.lock;
2+
3+
import jakarta.persistence.LockModeType;
4+
import org.hibernate.cfg.AvailableSettings;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
import tech.ydb.test.junit5.YdbHelperExtension;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertThrows;
11+
import static tech.ydb.hibernate.TestUtils.*;
12+
import static tech.ydb.hibernate.dialect.YdbSettings.IGNORE_LOCK_HINTS;
13+
14+
/**
15+
* Tests for {@value tech.ydb.hibernate.dialect.YdbSettings#IGNORE_LOCK_HINTS} setting.
16+
*
17+
* @author Ainur Mukhtarov
18+
*/
19+
public class LockTests {
20+
21+
private static final long ENTITY_ID = 1L;
22+
23+
@RegisterExtension
24+
private static final YdbHelperExtension ydb = new YdbHelperExtension();
25+
26+
@Test
27+
void whenIgnoreLockHintsTrue_pessimisticLockSucceeds() {
28+
runWithConfiguration(true, () -> {
29+
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_WRITE);
30+
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_READ);
31+
assertEntityFoundWithLock(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
32+
});
33+
}
34+
35+
@Test
36+
void whenIgnoreLockHintsFalse_pessimisticLockThrows() {
37+
runWithConfiguration(false, () -> {
38+
assertThrowsWithLock(LockModeType.PESSIMISTIC_WRITE);
39+
assertThrowsWithLock(LockModeType.PESSIMISTIC_READ);
40+
assertThrowsWithLock(LockModeType.PESSIMISTIC_FORCE_INCREMENT);
41+
});
42+
}
43+
44+
private void persistEntity() {
45+
var entity = new TestEntity();
46+
entity.setId(ENTITY_ID);
47+
entity.setStringValue("test");
48+
49+
inTransaction(session -> session.persist(entity));
50+
}
51+
52+
private static void assertEntityFoundWithLock(LockModeType lockMode) {
53+
inTransaction(session -> {
54+
var actual = session.find(TestEntity.class, ENTITY_ID, lockMode);
55+
56+
assertEquals(ENTITY_ID, actual.getId());
57+
assertEquals("test", actual.getStringValue());
58+
});
59+
}
60+
61+
private static void assertThrowsWithLock(LockModeType pessimisticForceIncrement) {
62+
assertThrows(
63+
UnsupportedOperationException.class,
64+
() -> inTransaction(s -> s.find(TestEntity.class, ENTITY_ID, pessimisticForceIncrement)),
65+
"YDB does not support FOR UPDATE statement"
66+
);
67+
}
68+
69+
private void runWithConfiguration(boolean settingValue, Runnable test) {
70+
SESSION_FACTORY = basedConfiguration()
71+
.setProperty(AvailableSettings.URL, jdbcUrl(ydb))
72+
.addAnnotatedClass(TestEntity.class)
73+
.setProperty(IGNORE_LOCK_HINTS, settingValue)
74+
.buildSessionFactory();
75+
try {
76+
persistEntity();
77+
78+
test.run();
79+
} finally {
80+
inTransaction(session -> {
81+
var entity = session.find(TestEntity.class, ENTITY_ID);
82+
if (entity != null) {
83+
session.remove(entity);
84+
}
85+
});
86+
if (SESSION_FACTORY != null) {
87+
SESSION_FACTORY.close();
88+
}
89+
}
90+
}
91+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package tech.ydb.hibernate.lock;
2+
3+
import jakarta.persistence.*;
4+
import lombok.Data;
5+
6+
@Entity
7+
@Data
8+
@Table(name = "lock_test_entity")
9+
public class TestEntity {
10+
@Id
11+
private Long id;
12+
13+
@Column(name = "string_value", nullable = false)
14+
private String stringValue;
15+
16+
@Version
17+
@Column(name = "version")
18+
private int version;
19+
}

0 commit comments

Comments
 (0)