Skip to content

Commit 76877b9

Browse files
artembilangaryrussell
authored andcommitted
INT-4499: Add JdbcMetadataStore.setLockHint()
JIRA https://jira.spring.io/browse/INT-4499 Fixes #2483 To allow to reconfigure a `SELECT ... FOR UPDATE` for particular RDBMS vendor, we need an option to specify possible hint for the target data base. **Cherry-pick to 5.0.x**
1 parent 0d11c7e commit 76877b9

File tree

3 files changed

+45
-28
lines changed

3 files changed

+45
-28
lines changed

spring-integration-file/src/test/java/org/springframework/integration/file/filters/PersistentAcceptOnceFileListFilterExternalStoreTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2017 the original author or authors.
2+
* Copyright 2014-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ public class PersistentAcceptOnceFileListFilterExternalStoreTests extends RedisA
5858
@Test
5959
@RedisAvailable
6060
public void testFileSystemWithRedisMetadataStore() throws Exception {
61-
RedisTemplate<String, ?> template = new RedisTemplate<String, Object>();
61+
RedisTemplate<String, ?> template = new RedisTemplate<>();
6262
template.setConnectionFactory(this.getConnectionFactoryForTest());
6363
template.setKeySerializer(new StringRedisSerializer());
6464
template.afterPropertiesSet();
@@ -87,6 +87,7 @@ public void testFileSystemWithJdbcMetadataStore() throws Exception {
8787
.build();
8888

8989
JdbcMetadataStore metadataStore = new JdbcMetadataStore(dataSource);
90+
metadataStore.setLockHint("");
9091
metadataStore.afterPropertiesSet();
9192

9293
try {

spring-integration-jdbc/src/main/java/org/springframework/integration/jdbc/metadata/JdbcMetadataStore.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,32 +49,24 @@ public class JdbcMetadataStore implements ConcurrentMetadataStore, InitializingB
4949

5050
private final JdbcOperations jdbcTemplate;
5151

52-
private volatile String tablePrefix = DEFAULT_TABLE_PREFIX;
52+
private String tablePrefix = DEFAULT_TABLE_PREFIX;
5353

54-
private volatile String region = "DEFAULT";
54+
private String region = "DEFAULT";
5555

56-
private String getValueQuery = "SELECT METADATA_VALUE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
56+
private String lockHint = "FOR UPDATE";
5757

58-
private String getValueForUpdateQuery = "SELECT METADATA_VALUE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? FOR UPDATE";
58+
private String getValueQuery = "SELECT METADATA_VALUE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
5959

60-
private String replaceValueQuery = "UPDATE %SMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND METADATA_VALUE=? AND REGION=?";
60+
private String getValueForUpdateQuery = "SELECT METADATA_VALUE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? %s";
6161

62-
private String replaceValueByKeyQuery = "UPDATE %SMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND REGION=?";
62+
private String replaceValueQuery = "UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND METADATA_VALUE=? AND REGION=?";
6363

64-
private String removeValueQuery = "DELETE FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
64+
private String replaceValueByKeyQuery = "UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND REGION=?";
6565

66-
private String putIfAbsentValueQuery = "INSERT INTO %SMETADATA_STORE(METADATA_KEY, METADATA_VALUE, REGION) "
67-
+ "SELECT ?, ?, ? FROM %SMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? HAVING COUNT(*)=0";
66+
private String removeValueQuery = "DELETE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
6867

69-
@Override
70-
public void afterPropertiesSet() throws Exception {
71-
this.getValueQuery = String.format(this.getValueQuery, this.tablePrefix);
72-
this.getValueForUpdateQuery = String.format(this.getValueForUpdateQuery, this.tablePrefix);
73-
this.replaceValueQuery = String.format(this.replaceValueQuery, this.tablePrefix);
74-
this.replaceValueByKeyQuery = String.format(this.replaceValueByKeyQuery, this.tablePrefix);
75-
this.removeValueQuery = String.format(this.removeValueQuery, this.tablePrefix);
76-
this.putIfAbsentValueQuery = String.format(this.putIfAbsentValueQuery, this.tablePrefix, this.tablePrefix);
77-
}
68+
private String putIfAbsentValueQuery = "INSERT INTO %sMETADATA_STORE(METADATA_KEY, METADATA_VALUE, REGION) "
69+
+ "SELECT ?, ?, ? FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? HAVING COUNT(*)=0";
7870

7971
/**
8072
* Instantiate a {@link JdbcMetadataStore} using provided dataSource {@link DataSource}.
@@ -89,7 +81,7 @@ public JdbcMetadataStore(DataSource dataSource) {
8981
* @param jdbcOperations a {@link JdbcOperations}
9082
*/
9183
public JdbcMetadataStore(JdbcOperations jdbcOperations) {
92-
Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null");
84+
Assert.notNull(jdbcOperations, "'jdbcOperations' must not be null.");
9385
this.jdbcTemplate = jdbcOperations;
9486
}
9587

@@ -100,7 +92,7 @@ public JdbcMetadataStore(JdbcOperations jdbcOperations) {
10092
* @param tablePrefix the tablePrefix to set
10193
*/
10294
public void setTablePrefix(String tablePrefix) {
103-
Assert.notNull(tablePrefix, "'tablePrefix' must not be null");
95+
Assert.notNull(tablePrefix, "'tablePrefix' must not be null.");
10496
this.tablePrefix = tablePrefix;
10597
}
10698

@@ -112,10 +104,33 @@ public void setTablePrefix(String tablePrefix) {
112104
* @param region the region name to set
113105
*/
114106
public void setRegion(String region) {
115-
Assert.hasText(region, "Region must not be null or empty.");
107+
Assert.hasText(region, "'region' must not be null or empty.");
116108
this.region = region;
117109
}
118110

111+
/**
112+
* Specify a row lock hint for the query in the lock-based operations.
113+
* Defaults to {@code FOR UPDATE}. Can be specified as an empty string,
114+
* if the target RDBMS doesn't support locking on tables from queries.
115+
* The value depends from RDBMS vendor, e.g. SQL Server requires {@code WITH (ROWLOCK)}.
116+
* @param lockHint the RDBMS vendor-specific lock hint.
117+
* @since 5.0.7
118+
*/
119+
public void setLockHint(String lockHint) {
120+
Assert.notNull(lockHint, "'lockHint' cannot be null.");
121+
this.lockHint = lockHint;
122+
}
123+
124+
@Override
125+
public void afterPropertiesSet() {
126+
this.getValueQuery = String.format(this.getValueQuery, this.tablePrefix);
127+
this.getValueForUpdateQuery = String.format(this.getValueForUpdateQuery, this.tablePrefix, this.lockHint);
128+
this.replaceValueQuery = String.format(this.replaceValueQuery, this.tablePrefix);
129+
this.replaceValueByKeyQuery = String.format(this.replaceValueByKeyQuery, this.tablePrefix);
130+
this.removeValueQuery = String.format(this.removeValueQuery, this.tablePrefix);
131+
this.putIfAbsentValueQuery = String.format(this.putIfAbsentValueQuery, this.tablePrefix, this.tablePrefix);
132+
}
133+
119134
@Override
120135
@Transactional
121136
public String putIfAbsent(String key, String value) {

src/reference/asciidoc/jdbc.adoc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ The following example show to use the `query` attribute:
320320
reply-channel="output"
321321
data-source="dataSource"/>
322322
----
323+
====
323324

324325
[IMPORTANT]
325326
====
@@ -328,10 +329,6 @@ You can adjust this behavior with the `max-rows` option.
328329
If you need to return all the rows from the SELECT, consider specifying `max-rows="0"`.
329330
====
330331

331-
IMPORTANT: By default, the component for the `SELECT` query returns only the first row from the cursor.
332-
You can adjust this by setting the `max-rows-per-poll` option.
333-
If you need to return all the rows from the `SELECT`, you can set `max-rows-per-poll="0"`.
334-
335332
As with the channel adapters, you can also provide `SqlParameterSourceFactory` instances for request and reply.
336333
The default is the same as for the outbound adapter, so the request message is available as the root of an expression.
337334
If `keys-generated="true"`, the root of the expression is the generated keys (a map if there is only one or a list of maps if multi-valued).
@@ -1111,3 +1108,7 @@ Transaction management must use `JdbcMetadataStore`.
11111108
Inbound channel adapters can be supplied with a reference to the `TransactionManager` in the poller configuration.
11121109
Unlike non-transactional `MetadataStore` implementations, with `JdbcMetadataStore`, the entry appears in the target table only after the transaction commits.
11131110
When a rollback occurs, no entries are added to the `INT_METADATA_STORE` table.
1111+
1112+
Since version 5.0.7, the `JdbcMetadataStore` can be configured with the RDBMS vendor-specific `lockHint` option for lock-based queries on metadata store entries.
1113+
It is `FOR UPDATE` by default and can be configured with an empty string, if the target data base doesn't support row locking functionality.
1114+
Please, consult with your vendor for particular possible hint in the `SELECT` expression for locking rows before updates.

0 commit comments

Comments
 (0)