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:
committed by
Gary Russell
parent
42845f03f3
commit
aec7bed522
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user