Skip to content

GH-3866: DefLockRepository: expose query setters #8606

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -168,11 +167,11 @@ public void setPrefix(String prefix) {
}

/**
* 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);
}

/**
Expand All @@ -191,6 +190,99 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
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);
Expand Down Expand Up @@ -249,14 +341,13 @@ public boolean acquire(String lock) {
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;
Expand All @@ -272,24 +363,22 @@ public boolean isAcquired(String lock) {
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);
}

@Override
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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/>
Expand Down
13 changes: 12 additions & 1 deletion src/reference/asciidoc/jdbc.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down
7 changes: 7 additions & 0 deletions src/reference/asciidoc/whats-new.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,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.