Browse Source
The previous process of deleting referencing entities, updating the root and then inserting referencing entities hat the potential of causing deadlocks. When one process didn't obtain a lock on delete because there wasn't anything to delete root and referencing entities got locks in opposite order, which is a classical cause for deadlocks. Original pull request: #191.pull/195/head
5 changed files with 161 additions and 13 deletions
@ -0,0 +1,140 @@ |
|||||||
|
package org.springframework.data.jdbc.repository; |
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError; |
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Getter; |
||||||
|
import lombok.With; |
||||||
|
import org.junit.ClassRule; |
||||||
|
import org.junit.Rule; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
import org.springframework.data.annotation.Id; |
||||||
|
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; |
||||||
|
import org.springframework.data.jdbc.testing.TestConfiguration; |
||||||
|
import org.springframework.data.repository.CrudRepository; |
||||||
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; |
||||||
|
import org.springframework.test.context.ActiveProfiles; |
||||||
|
import org.springframework.test.context.ContextConfiguration; |
||||||
|
import org.springframework.test.context.junit4.rules.SpringClassRule; |
||||||
|
import org.springframework.test.context.junit4.rules.SpringMethodRule; |
||||||
|
import org.springframework.transaction.PlatformTransactionManager; |
||||||
|
import org.springframework.transaction.support.TransactionTemplate; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.CopyOnWriteArrayList; |
||||||
|
import java.util.concurrent.CountDownLatch; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Myeonghyeon Lee |
||||||
|
*/ |
||||||
|
@ContextConfiguration |
||||||
|
@ActiveProfiles("mysql") |
||||||
|
public class JdbcRepositoryConcurrencyIntegrationTests { |
||||||
|
@Configuration |
||||||
|
@Import(TestConfiguration.class) |
||||||
|
static class Config { |
||||||
|
|
||||||
|
@Autowired JdbcRepositoryFactory factory; |
||||||
|
|
||||||
|
@Bean |
||||||
|
Class<?> testClass() { |
||||||
|
return JdbcRepositoryConcurrencyIntegrationTests.class; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
DummyEntityRepository dummyEntityRepository() { |
||||||
|
return factory.getRepository(DummyEntityRepository.class); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@ClassRule |
||||||
|
public static final SpringClassRule classRule = new SpringClassRule(); |
||||||
|
@Rule |
||||||
|
public SpringMethodRule methodRule = new SpringMethodRule(); |
||||||
|
|
||||||
|
@Autowired |
||||||
|
NamedParameterJdbcTemplate template; |
||||||
|
@Autowired |
||||||
|
DummyEntityRepository repository; |
||||||
|
@Autowired |
||||||
|
PlatformTransactionManager transactionManager; |
||||||
|
|
||||||
|
@Test // DATAJDBC-488
|
||||||
|
public void updateConcurrencyWithEmptyReferences() throws Exception { |
||||||
|
DummyEntity entity = createDummyEntity(); |
||||||
|
entity = repository.save(entity); |
||||||
|
|
||||||
|
assertThat(entity.getId()).isNotNull(); |
||||||
|
|
||||||
|
List<DummyEntity> concurrencyEntities = new ArrayList<>(); |
||||||
|
Element element1 = new Element(null, 1L); |
||||||
|
Element element2 = new Element(null, 2L); |
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) { |
||||||
|
List<Element> newContent = Arrays.asList( |
||||||
|
element1.withContent(element1.content + i + 2), |
||||||
|
element2.withContent(element2.content + i + 2) |
||||||
|
); |
||||||
|
|
||||||
|
concurrencyEntities.add(entity |
||||||
|
.withName(entity.getName() + i) |
||||||
|
.withContent(newContent)); |
||||||
|
} |
||||||
|
|
||||||
|
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager); |
||||||
|
|
||||||
|
List<Exception> exceptions = new CopyOnWriteArrayList<>(); |
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(concurrencyEntities.size()); |
||||||
|
concurrencyEntities.stream() |
||||||
|
.map(e -> new Thread(() -> { |
||||||
|
countDownLatch.countDown(); |
||||||
|
try { |
||||||
|
transactionTemplate.execute(status -> repository.save(e)); |
||||||
|
} catch (Exception ex) { |
||||||
|
exceptions.add(ex); |
||||||
|
} |
||||||
|
})) |
||||||
|
.forEach(Thread::start); |
||||||
|
|
||||||
|
countDownLatch.await(); |
||||||
|
|
||||||
|
Thread.sleep(1000); |
||||||
|
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); |
||||||
|
assertThat(reloaded.content).hasSize(2); |
||||||
|
assertThat(exceptions).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
private static DummyEntity createDummyEntity() { |
||||||
|
return new DummyEntity(null, "Entity Name", new ArrayList<>()); |
||||||
|
} |
||||||
|
|
||||||
|
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> { |
||||||
|
} |
||||||
|
|
||||||
|
@Getter |
||||||
|
@AllArgsConstructor |
||||||
|
static class DummyEntity { |
||||||
|
|
||||||
|
@Id |
||||||
|
private Long id; |
||||||
|
@With |
||||||
|
String name; |
||||||
|
@With |
||||||
|
final List<Element> content; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@AllArgsConstructor |
||||||
|
static class Element { |
||||||
|
|
||||||
|
@Id private Long id; |
||||||
|
@With final Long content; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,2 @@ |
|||||||
|
CREATE TABLE dummy_entity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100)); |
||||||
|
CREATE TABLE element (id BIGINT AUTO_INCREMENT PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT); |
||||||
Loading…
Reference in new issue