GH-3866: DefLockRepository: expose query setters (#8606)
Fixes https://github.com/spring-projects/spring-integration/issues/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`
This commit is contained in:
@@ -16,9 +16,8 @@
|
||||
|
||||
package org.springframework.integration.jdbc.lock;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -66,13 +65,13 @@ public class DefaultLockRepository
|
||||
/**
|
||||
* Default value for the time-to-live property.
|
||||
*/
|
||||
public static final int DEFAULT_TTL = 10000;
|
||||
public static final Duration DEFAULT_TTL = Duration.ofSeconds(10);
|
||||
|
||||
private final String id;
|
||||
|
||||
private final JdbcTemplate template;
|
||||
|
||||
private int ttl = DEFAULT_TTL;
|
||||
private Duration ttl = DEFAULT_TTL;
|
||||
|
||||
private String prefix = DEFAULT_TABLE_PREFIX;
|
||||
|
||||
@@ -168,11 +167,11 @@ public class DefaultLockRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the time (in milliseconds) to expire dead-locks.
|
||||
* @param timeToLive the time to expire dead-locks.
|
||||
* Specify the time (in milliseconds) to expire deadlocks.
|
||||
* @param timeToLive the time to expire deadlocks.
|
||||
*/
|
||||
public void setTimeToLive(int timeToLive) {
|
||||
this.ttl = timeToLive;
|
||||
this.ttl = Duration.ofMillis(timeToLive);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +190,99 @@ public class DefaultLockRepository
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@code UPDATE} query for a lock record.
|
||||
* The {@link #getUpdateQuery()} can be used as a template for customization.
|
||||
* The default query is:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* UPDATE %sLOCK
|
||||
* SET CLIENT_ID=?, CREATED_DATE=?
|
||||
* WHERE REGION=? AND LOCK_KEY=? AND (CLIENT_ID=? OR CREATED_DATE<?)
|
||||
* }
|
||||
* </pre>
|
||||
* @param updateQuery the query to update a lock record.
|
||||
* @since 6.1
|
||||
* @see #getUpdateQuery()
|
||||
* @see #setPrefix(String)
|
||||
*/
|
||||
public void setUpdateQuery(String updateQuery) {
|
||||
this.updateQuery = updateQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current update query.
|
||||
* Can be used in a setter as a concatenation of the default query and some extra hint.
|
||||
* @return the current update query.
|
||||
* @since 6.1
|
||||
* @see #setUpdateQuery(String)
|
||||
*/
|
||||
public String getUpdateQuery() {
|
||||
return this.updateQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@code INSERT} query for a lock record.
|
||||
* The {@link #getInsertQuery()} can be used as a template for customization.
|
||||
* The default query is
|
||||
* {@code INSERT INTO %sLOCK (REGION, LOCK_KEY, CLIENT_ID, CREATED_DATE) VALUES (?, ?, ?, ?)}.
|
||||
* For example a PostgreSQL {@code ON CONFLICT DO NOTHING} hint can be provided like this:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* lockRepository.setInsertQuery(lockRepository.getInsertQuery() + " ON CONFLICT DO NOTHING");
|
||||
* }
|
||||
* </pre>
|
||||
* @param insertQuery the insert query for a lock record.
|
||||
* @since 6.1
|
||||
* @see #getInsertQuery()
|
||||
* @see #setPrefix(String)
|
||||
*/
|
||||
public void setInsertQuery(String insertQuery) {
|
||||
this.insertQuery = insertQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current insert query.
|
||||
* Can be used in a setter as a concatenation of the default query and some extra hint.
|
||||
* @return the current insert query.
|
||||
* @since 6.1
|
||||
* @see #setInsertQuery(String)
|
||||
*/
|
||||
public String getInsertQuery() {
|
||||
return this.insertQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@code INSERT} query for a lock record.
|
||||
* The {@link #getRenewQuery()} can be used as a template for customization.
|
||||
* The default query is:
|
||||
* <pre class="code">
|
||||
* {@code
|
||||
* UPDATE %sLOCK
|
||||
* SET CREATED_DATE=?
|
||||
* WHERE REGION=? AND LOCK_KEY=? AND CLIENT_ID=?
|
||||
* }
|
||||
* </pre>
|
||||
* @param renewQuery the update query to renew a lock record.
|
||||
* @since 6.1
|
||||
* @see #getRenewQuery()
|
||||
* @see #setPrefix(String)
|
||||
*/
|
||||
public void setRenewQuery(String renewQuery) {
|
||||
this.renewQuery = renewQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current renew query.
|
||||
* Can be used in a setter as a concatenation of a default query and some extra hint.
|
||||
* @return the current renew query.
|
||||
* @since 6.1
|
||||
* @see #setRenewQuery(String)
|
||||
*/
|
||||
public String getRenewQuery() {
|
||||
return this.renewQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.deleteQuery = String.format(this.deleteQuery, this.prefix);
|
||||
@@ -249,14 +341,13 @@ public class DefaultLockRepository
|
||||
Boolean result =
|
||||
this.serializableTransactionTemplate.execute(
|
||||
transactionStatus -> {
|
||||
if (this.template.update(this.updateQuery, this.id, LocalDateTime.now(ZoneOffset.UTC),
|
||||
this.region, lock, this.id,
|
||||
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS)) > 0) {
|
||||
if (this.template.update(this.updateQuery, this.id, Instant.now(),
|
||||
this.region, lock, this.id, Instant.now().minus(this.ttl)) > 0) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return this.template.update(this.insertQuery, this.region, lock, this.id,
|
||||
LocalDateTime.now(ZoneOffset.UTC)) > 0;
|
||||
Instant.now()) > 0;
|
||||
}
|
||||
catch (DataIntegrityViolationException ex) {
|
||||
return false;
|
||||
@@ -272,7 +363,7 @@ public class DefaultLockRepository
|
||||
Integer.valueOf(1).equals(
|
||||
this.template.queryForObject(this.countQuery,
|
||||
Integer.class, this.region, lock, this.id,
|
||||
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS))));
|
||||
Instant.now().minus(this.ttl))));
|
||||
return Boolean.TRUE.equals(result);
|
||||
}
|
||||
|
||||
@@ -280,16 +371,14 @@ public class DefaultLockRepository
|
||||
public void deleteExpired() {
|
||||
this.defaultTransactionTemplate.executeWithoutResult(
|
||||
transactionStatus ->
|
||||
this.template.update(this.deleteExpiredQuery, this.region,
|
||||
LocalDateTime.now(ZoneOffset.UTC).minus(this.ttl, ChronoUnit.MILLIS)));
|
||||
this.template.update(this.deleteExpiredQuery, this.region, Instant.now().minus(this.ttl)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renew(String lock) {
|
||||
final Boolean result = this.defaultTransactionTemplate.execute(
|
||||
transactionStatus ->
|
||||
this.template.update(this.renewQuery, LocalDateTime.now(ZoneOffset.UTC),
|
||||
this.region, lock, this.id) > 0);
|
||||
this.template.update(this.renewQuery, Instant.now(), this.region, lock, this.id) > 0);
|
||||
return Boolean.TRUE.equals(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
<bean id="lockClient" class="org.springframework.integration.jdbc.lock.DefaultLockRepository">
|
||||
<constructor-arg name="dataSource" ref="dataSource"/>
|
||||
<property name="insertQuery"
|
||||
value="INSERT INTO INT_LOCK (REGION, LOCK_KEY, CLIENT_ID, CREATED_DATE) VALUES (?, ?, ?, ?)"/>
|
||||
</bean>
|
||||
|
||||
<tx:annotation-driven/>
|
||||
|
||||
@@ -1143,7 +1143,7 @@ The `INT_` can be changed according to the target database design requirements.
|
||||
Therefore, you must use `prefix` property on the `DefaultLockRepository` bean definition.
|
||||
|
||||
Sometimes, one application has moved to such a state that it cannot release the distributed lock and remove the particular record in the database.
|
||||
For this purpose, such dead-locks can be expired by the other application on the next locking invocation.
|
||||
For this purpose, such deadlocks can be expired by the other application on the next locking invocation.
|
||||
The `timeToLive` (TTL) option on the `DefaultLockRepository` is provided for this purpose.
|
||||
You may also want to specify `CLIENT_ID` for the locks stored for a given `DefaultLockRepository` instance.
|
||||
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.
|
||||
|
||||
String with version 6.0, the `DefaultLockRepository` can be supplied with a `PlatformTransactionManager` instead of relying on the primary bean from the application context.
|
||||
|
||||
String with version 6.1, the `DefaultLockRepository` can be configured for custom `insert`, `update` and `renew` queries.
|
||||
For this purpose the respective setters and getters are exposed.
|
||||
For example, an insert query for PostgreSQL hint can be configured like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
lockRepository.setInsertQuery(lockRepository.getInsertQuery() + " ON CONFLICT DO NOTHING");
|
||||
----
|
||||
====
|
||||
|
||||
[[jdbc-metadata-store]]
|
||||
=== JDBC Metadata Store
|
||||
|
||||
|
||||
@@ -80,3 +80,10 @@ See <<./file.adoc#watch-service-directory-scanner, `WatchServiceDirectoryScanner
|
||||
|
||||
The Java DSL API for Rabbit Streams (the `RabbitStream` factory) exposes additional properties for simple configurations.
|
||||
See <<./amqp.adoc#rmq-streams, `RabbitMQ Stream Queue Support`>> for more information.
|
||||
|
||||
|
||||
[[x6.1-jdbc]]
|
||||
=== JDBC Changes
|
||||
|
||||
The `DefaultLockRepository` now exposes setters for `insert`, `update` and `renew` queries.
|
||||
See <<./jdbc.adoc#jdbc-lock-registry, JDBC Lock Registry>> for more information.
|
||||
|
||||
Reference in New Issue
Block a user