From 8b9acccfacd7ec234a41e5b7bd1b2ee6e2f20309 Mon Sep 17 00:00:00 2001 From: abilan Date: Fri, 28 Apr 2023 13:21:18 -0400 Subject: [PATCH] GH-3866: DefLockRepository: expose query setters 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` --- .../jdbc/lock/DefaultLockRepository.java | 123 +++++++++++++++--- .../lock/JdbcLockRegistryTests-context.xml | 2 + src/reference/asciidoc/jdbc.adoc | 13 +- src/reference/asciidoc/whats-new.adoc | 7 + 4 files changed, 127 insertions(+), 18 deletions(-) diff --git a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/DefaultLockRepository.java b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/DefaultLockRepository.java index cb06d50fb6f..1b7e9c5d06f 100644 --- a/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/DefaultLockRepository.java +++ b/spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/lock/DefaultLockRepository.java @@ -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 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); } /** @@ -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: + *
+	 * {@code
+	 *  UPDATE %sLOCK
+	 * 			SET CLIENT_ID=?, CREATED_DATE=?
+	 * 			WHERE REGION=? AND LOCK_KEY=? AND (CLIENT_ID=? OR CREATED_DATE
+	 * @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:
+	 * 
+	 * {@code
+	 *  lockRepository.setInsertQuery(lockRepository.getInsertQuery() + " ON CONFLICT DO NOTHING");
+	 * }
+	 * 
+ * @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: + *
+	 * {@code
+	 *  UPDATE %sLOCK
+	 * 			SET CREATED_DATE=?
+	 * 			WHERE REGION=? AND LOCK_KEY=? AND CLIENT_ID=?
+	 * }
+	 * 
+ * @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 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; @@ -272,7 +363,7 @@ 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); } @@ -280,16 +371,14 @@ public boolean isAcquired(String lock) { 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); } diff --git a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests-context.xml b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests-context.xml index 4d4511c095e..0dc9eab475e 100644 --- a/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests-context.xml +++ b/spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests-context.xml @@ -24,6 +24,8 @@ + diff --git a/src/reference/asciidoc/jdbc.adoc b/src/reference/asciidoc/jdbc.adoc index 069e1a0898e..39e4464261d 100644 --- a/src/reference/asciidoc/jdbc.adoc +++ b/src/reference/asciidoc/jdbc.adoc @@ -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 diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index c5b60ae53fb..d955d0e10fc 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -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.