GH-3282: Fix JdbcMetadataStore for DuplicateKeyEx (#3285)

* GH-3282: Fix JdbcMetadataStore for DuplicateKeyEx

Fixes: https://github.com/spring-projects/spring-integration/issues/3282

The `INSERT INTO ... SELECT ... FROM ... HAVING` may fail with
`DuplicateKeyException` in between transactions.
* Catch `DuplicateKeyException` from in the `tryToPutIfAbsent()`
and return `0` as a fact of not inserted to let other logic to work
as expected.

There is no test coverage for this since it is almost impossible to
reproduce such a race condition on DB

**Cherry-pick to 5.3.x & 5.2.x**

* * Remove misleading message in the comment sentence
This commit is contained in:
Artem Bilan
2020-05-20 14:45:45 -04:00
committed by Gary Russell
parent 42845f03f3
commit aec7bed522
2 changed files with 29 additions and 22 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.jdbc.metadata;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.integration.metadata.ConcurrentMetadataStore;
import org.springframework.jdbc.core.JdbcOperations;
@@ -59,16 +60,20 @@ public class JdbcMetadataStore implements ConcurrentMetadataStore, InitializingB
private String getValueQuery = "SELECT METADATA_VALUE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
private String getValueForUpdateQuery = "SELECT METADATA_VALUE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? %s";
private String getValueForUpdateQuery =
"SELECT METADATA_VALUE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? %s";
private String replaceValueQuery = "UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND METADATA_VALUE=? AND REGION=?";
private String replaceValueQuery =
"UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND METADATA_VALUE=? AND REGION=?";
private String replaceValueByKeyQuery = "UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND REGION=?";
private String replaceValueByKeyQuery =
"UPDATE %sMETADATA_STORE SET METADATA_VALUE=? WHERE METADATA_KEY=? AND REGION=?";
private String removeValueQuery = "DELETE FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=?";
private String putIfAbsentValueQuery = "INSERT INTO %sMETADATA_STORE(METADATA_KEY, METADATA_VALUE, REGION) "
+ "SELECT ?, ?, ? FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? HAVING COUNT(*)=0";
private String putIfAbsentValueQuery =
"INSERT INTO %sMETADATA_STORE(METADATA_KEY, METADATA_VALUE, REGION) "
+ "SELECT ?, ?, ? FROM %sMETADATA_STORE WHERE METADATA_KEY=? AND REGION=? HAVING COUNT(*)=0";
/**
* Instantiate a {@link JdbcMetadataStore} using provided dataSource {@link DataSource}.
@@ -142,7 +147,7 @@ public class JdbcMetadataStore implements ConcurrentMetadataStore, InitializingB
//try to insert if does not exists
int affectedRows = tryToPutIfAbsent(key, value);
if (affectedRows > 0) {
//it was not in the table, so we have just inserted it
//it was not in the table, so we have just inserted
return null;
}
else {
@@ -158,14 +163,19 @@ public class JdbcMetadataStore implements ConcurrentMetadataStore, InitializingB
}
private int tryToPutIfAbsent(String key, String value) {
return this.jdbcTemplate.update(this.putIfAbsentValueQuery,
ps -> {
ps.setString(1, key);
ps.setString(2, value);
ps.setString(3, this.region);
ps.setString(4, key);
ps.setString(5, this.region);
});
try {
return this.jdbcTemplate.update(this.putIfAbsentValueQuery,
ps -> {
ps.setString(1, key);
ps.setString(2, value);
ps.setString(3, this.region);
ps.setString(4, key);
ps.setString(5, this.region);
});
}
catch (DuplicateKeyException ex) {
return 0;
}
}
@Override

View File

@@ -20,14 +20,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -36,8 +34,7 @@ import org.springframework.transaction.annotation.Transactional;
*
* @since 5.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@SpringJUnitConfig
@DirtiesContext // close at the end after class
@Transactional
public class JdbcMetadataStoreTests {
@@ -48,7 +45,7 @@ public class JdbcMetadataStoreTests {
private JdbcMetadataStore metadataStore;
@Before
@BeforeEach
public void init() {
metadataStore = new JdbcMetadataStore(dataSource);
metadataStore.afterPropertiesSet();