Skip to content

Commit 90bc65e

Browse files
authored
GH-3866: DefLockRepository: expose query setters (#8606)
Fixes #3866 Some RDBMS vendors (or their JDBC drivers) may just log the problem without throwing an exception. * Expose setters for UPDATE and INSERTS queries in the `DefaultLockRepository` to let end-user to modify them respectively, e.g. be able to add a PostgreSQL `ON CONFLICT DO NOTHING` hint. * Refactor `DefaultLockRepository` to `Instant` instead of `LocalDateTime` with zone offset. * Refactor `ttl` property to `Duration` type * Fix `dead-lock` typo to `deadlock`
1 parent 33d13a4 commit 90bc65e

File tree

4 files changed

+127
-18
lines changed

4 files changed

+127
-18
lines changed

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/DefaultLockRepository.java

+106-17
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
package org.springframework.integration.jdbc.lock;
1818

19-
import java.time.LocalDateTime;
20-
import java.time.ZoneOffset;
21-
import java.time.temporal.ChronoUnit;
19+
import java.time.Duration;
20+
import java.time.Instant;
2221
import java.util.UUID;
2322

2423
import javax.sql.DataSource;
@@ -66,13 +65,13 @@ public class DefaultLockRepository
6665
/**
6766
* Default value for the time-to-live property.
6867
*/
69-
public static final int DEFAULT_TTL = 10000;
68+
public static final Duration DEFAULT_TTL = Duration.ofSeconds(10);
7069

7170
private final String id;
7271

7372
private final JdbcTemplate template;
7473

75-
private int ttl = DEFAULT_TTL;
74+
private Duration ttl = DEFAULT_TTL;
7675

7776
private String prefix = DEFAULT_TABLE_PREFIX;
7877

@@ -168,11 +167,11 @@ public void setPrefix(String prefix) {
168167
}
169168

170169
/**
171-
* Specify the time (in milliseconds) to expire dead-locks.
172-
* @param timeToLive the time to expire dead-locks.
170+
* Specify the time (in milliseconds) to expire deadlocks.
171+
* @param timeToLive the time to expire deadlocks.
173172
*/
174173
public void setTimeToLive(int timeToLive) {
175-
this.ttl = timeToLive;
174+
this.ttl = Duration.ofMillis(timeToLive);
176175
}
177176

178177
/**
@@ -191,6 +190,99 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
191190
this.applicationContext = applicationContext;
192191
}
193192

193+
/**
194+
* Set a custom {@code UPDATE} query for a lock record.
195+
* The {@link #getUpdateQuery()} can be used as a template for customization.
196+
* The default query is:
197+
* <pre class="code">
198+
* {@code
199+
* UPDATE %sLOCK
200+
* SET CLIENT_ID=?, CREATED_DATE=?
201+
* WHERE REGION=? AND LOCK_KEY=? AND (CLIENT_ID=? OR CREATED_DATE<?)
202+
* }
203+
* </pre>
204+
* @param updateQuery the query to update a lock record.
205+
* @since 6.1
206+
* @see #getUpdateQuery()
207+
* @see #setPrefix(String)
208+
*/
209+
public void setUpdateQuery(String updateQuery) {
210+
this.updateQuery = updateQuery;
211+
}
212+
213+
/**
214+
* Return the current update query.
215+
* Can be used in a setter as a concatenation of the default query and some extra hint.
216+
* @return the current update query.
217+
* @since 6.1
218+
* @see #setUpdateQuery(String)
219+
*/
220+
public String getUpdateQuery() {
221+
return this.updateQuery;
222+
}
223+
224+
/**
225+
* Set a custom {@code INSERT} query for a lock record.
226+
* The {@link #getInsertQuery()} can be used as a template for customization.
227+
* The default query is
228+
* {@code INSERT INTO %sLOCK (REGION, LOCK_KEY, CLIENT_ID, CREATED_DATE) VALUES (?, ?, ?, ?)}.
229+
* For example a PostgreSQL {@code ON CONFLICT DO NOTHING} hint can be provided like this:
230+
* <pre class="code">
231+
* {@code
232+
* lockRepository.setInsertQuery(lockRepository.getInsertQuery() + " ON CONFLICT DO NOTHING");
233+
* }
234+
* </pre>
235+
* @param insertQuery the insert query for a lock record.
236+
* @since 6.1
237+
* @see #getInsertQuery()
238+
* @see #setPrefix(String)
239+
*/
240+
public void setInsertQuery(String insertQuery) {
241+
this.insertQuery = insertQuery;
242+
}
243+
244+
/**
245+
* Return the current insert query.
246+
* Can be used in a setter as a concatenation of the default query and some extra hint.
247+
* @return the current insert query.
248+
* @since 6.1
249+
* @see #setInsertQuery(String)
250+
*/
251+
public String getInsertQuery() {
252+
return this.insertQuery;
253+
}
254+
255+
/**
256+
* Set a custom {@code INSERT} query for a lock record.
257+
* The {@link #getRenewQuery()} can be used as a template for customization.
258+
* The default query is:
259+
* <pre class="code">
260+
* {@code
261+
* UPDATE %sLOCK
262+
* SET CREATED_DATE=?
263+
* WHERE REGION=? AND LOCK_KEY=? AND CLIENT_ID=?
264+
* }
265+
* </pre>
266+
* @param renewQuery the update query to renew a lock record.
267+
* @since 6.1
268+
* @see #getRenewQuery()
269+
* @see #setPrefix(String)
270+
*/
271+
public void setRenewQuery(String renewQuery) {
272+
this.renewQuery = renewQuery;
273+
}
274+
275+
/**
276+
* Return the current renew query.
277+
* Can be used in a setter as a concatenation of a default query and some extra hint.
278+
* @return the current renew query.
279+
* @since 6.1
280+
* @see #setRenewQuery(String)
281+
*/
282+
public String getRenewQuery() {
283+
return this.renewQuery;
284+
}
285+
194286
@Override
195287
public void afterPropertiesSet() {
196288
this.deleteQuery = String.format(this.deleteQuery, this.prefix);
@@ -249,14 +341,13 @@ public boolean acquire(String lock) {
249341
Boolean result =
250342
this.serializableTransactionTemplate.execute(
251343
transactionStatus -> {
252-
if (this.template.update(this.updateQuery, this.id, LocalDateTime.now(ZoneOffset.UTC),
253-
this.region, lock, this.id,
254-
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS)) > 0) {
344+
if (this.template.update(this.updateQuery, this.id, Instant.now(),
345+
this.region, lock, this.id, Instant.now().minus(this.ttl)) > 0) {
255346
return true;
256347
}
257348
try {
258349
return this.template.update(this.insertQuery, this.region, lock, this.id,
259-
LocalDateTime.now(ZoneOffset.UTC)) > 0;
350+
Instant.now()) > 0;
260351
}
261352
catch (DataIntegrityViolationException ex) {
262353
return false;
@@ -272,24 +363,22 @@ public boolean isAcquired(String lock) {
272363
Integer.valueOf(1).equals(
273364
this.template.queryForObject(this.countQuery,
274365
Integer.class, this.region, lock, this.id,
275-
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS))));
366+
Instant.now().minus(this.ttl))));
276367
return Boolean.TRUE.equals(result);
277368
}
278369

279370
@Override
280371
public void deleteExpired() {
281372
this.defaultTransactionTemplate.executeWithoutResult(
282373
transactionStatus ->
283-
this.template.update(this.deleteExpiredQuery, this.region,
284-
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS)));
374+
this.template.update(this.deleteExpiredQuery, this.region, Instant.now().minus(this.ttl)));
285375
}
286376

287377
@Override
288378
public boolean renew(String lock) {
289379
final Boolean result = this.defaultTransactionTemplate.execute(
290380
transactionStatus ->
291-
this.template.update(this.renewQuery, LocalDateTime.now(ZoneOffset.UTC),
292-
this.region, lock, this.id) > 0);
381+
this.template.update(this.renewQuery, Instant.now(), this.region, lock, this.id) > 0);
293382
return Boolean.TRUE.equals(result);
294383
}
295384

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests-context.xml

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
<bean id="lockClient" class="org.springframework.integration.jdbc.lock.DefaultLockRepository">
2626
<constructor-arg name="dataSource" ref="dataSource"/>
27+
<property name="insertQuery"
28+
value="INSERT INTO INT_LOCK (REGION, LOCK_KEY, CLIENT_ID, CREATED_DATE) VALUES (?, ?, ?, ?)"/>
2729
</bean>
2830

2931
<tx:annotation-driven/>

src/reference/asciidoc/jdbc.adoc

+12-1
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ The `INT_` can be changed according to the target database design requirements.
11431143
Therefore, you must use `prefix` property on the `DefaultLockRepository` bean definition.
11441144

11451145
Sometimes, one application has moved to such a state that it cannot release the distributed lock and remove the particular record in the database.
1146-
For this purpose, such dead-locks can be expired by the other application on the next locking invocation.
1146+
For this purpose, such deadlocks can be expired by the other application on the next locking invocation.
11471147
The `timeToLive` (TTL) option on the `DefaultLockRepository` is provided for this purpose.
11481148
You may also want to specify `CLIENT_ID` for the locks stored for a given `DefaultLockRepository` instance.
11491149
If so, you can specify the `id` to be associated with the `DefaultLockRepository` as a constructor parameter.
@@ -1162,6 +1162,17 @@ See its JavaDocs for more information.
11621162

11631163
String with version 6.0, the `DefaultLockRepository` can be supplied with a `PlatformTransactionManager` instead of relying on the primary bean from the application context.
11641164

1165+
String with version 6.1, the `DefaultLockRepository` can be configured for custom `insert`, `update` and `renew` queries.
1166+
For this purpose the respective setters and getters are exposed.
1167+
For example, an insert query for PostgreSQL hint can be configured like this:
1168+
1169+
====
1170+
[source,java]
1171+
----
1172+
lockRepository.setInsertQuery(lockRepository.getInsertQuery() + " ON CONFLICT DO NOTHING");
1173+
----
1174+
====
1175+
11651176
[[jdbc-metadata-store]]
11661177
=== JDBC Metadata Store
11671178

src/reference/asciidoc/whats-new.adoc

+7
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,10 @@ See <<./file.adoc#watch-service-directory-scanner, `WatchServiceDirectoryScanner
8080

8181
The Java DSL API for Rabbit Streams (the `RabbitStream` factory) exposes additional properties for simple configurations.
8282
See <<./amqp.adoc#rmq-streams, `RabbitMQ Stream Queue Support`>> for more information.
83+
84+
85+
[[x6.1-jdbc]]
86+
=== JDBC Changes
87+
88+
The `DefaultLockRepository` now exposes setters for `insert`, `update` and `renew` queries.
89+
See <<./jdbc.adoc#jdbc-lock-registry, JDBC Lock Registry>> for more information.

0 commit comments

Comments
 (0)