DATAJDBC-488 - Polishing.
JdbcRepositoryConcurrencyIntegrationTests now only runs with MySql instead of always with MySql. Also modified it to not use sleep. Copyright header added. Formatting. Original pull request: #191.
This commit is contained in:
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
@@ -13,9 +28,12 @@ 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.DatabaseProfileValueSource;
|
||||
import org.springframework.data.jdbc.testing.TestConfiguration;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.test.annotation.IfProfileValue;
|
||||
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.rules.SpringClassRule;
|
||||
@@ -31,12 +49,16 @@ import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
/** Tests that highly concurrent update operations of an entity don't cause deadlocks.
|
||||
*
|
||||
* @author Myeonghyeon Lee
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@ActiveProfiles("mysql")
|
||||
@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)
|
||||
@IfProfileValue(name = "current.database.is.not.mysql", value = "false")
|
||||
public class JdbcRepositoryConcurrencyIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import(TestConfiguration.class)
|
||||
static class Config {
|
||||
@@ -54,80 +76,82 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@ClassRule
|
||||
public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule
|
||||
public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
|
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
|
||||
|
||||
@Autowired
|
||||
NamedParameterJdbcTemplate template;
|
||||
@Autowired
|
||||
DummyEntityRepository repository;
|
||||
@Autowired
|
||||
PlatformTransactionManager transactionManager;
|
||||
@Autowired NamedParameterJdbcTemplate template;
|
||||
@Autowired DummyEntityRepository repository;
|
||||
@Autowired PlatformTransactionManager transactionManager;
|
||||
|
||||
@Test // DATAJDBC-488
|
||||
@Test // DATAJDBC-488
|
||||
public void updateConcurrencyWithEmptyReferences() throws Exception {
|
||||
|
||||
DummyEntity entity = createDummyEntity();
|
||||
entity = repository.save(entity);
|
||||
|
||||
assertThat(entity.getId()).isNotNull();
|
||||
|
||||
List<DummyEntity> concurrencyEntities = createEntityStates(entity);
|
||||
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
|
||||
|
||||
List<Exception> exceptions = new CopyOnWriteArrayList<>();
|
||||
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size()); // latch for all threads to wait on.
|
||||
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size()); // latch for main thread to wait on until all threads are done.
|
||||
|
||||
concurrencyEntities.stream() //
|
||||
.map(e -> new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
startLatch.countDown();
|
||||
startLatch.await();
|
||||
|
||||
transactionTemplate.execute(status -> repository.save(e));
|
||||
} catch (Exception ex) {
|
||||
exceptions.add(ex);
|
||||
} finally {
|
||||
doneLatch.countDown();
|
||||
}
|
||||
})) //
|
||||
.forEach(Thread::start);
|
||||
|
||||
doneLatch.await();
|
||||
|
||||
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
|
||||
assertThat(reloaded.content).hasSize(2);
|
||||
assertThat(exceptions).isEmpty();
|
||||
}
|
||||
|
||||
private List<DummyEntity> createEntityStates(DummyEntity entity) {
|
||||
|
||||
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));
|
||||
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();
|
||||
return concurrencyEntities;
|
||||
}
|
||||
|
||||
private static DummyEntity createDummyEntity() {
|
||||
return new DummyEntity(null, "Entity Name", new ArrayList<>());
|
||||
}
|
||||
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
|
||||
}
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
static class DummyEntity {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
@With
|
||||
String name;
|
||||
@With
|
||||
final List<Element> content;
|
||||
@Id private Long id;
|
||||
@With String name;
|
||||
@With final List<Element> content;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ public class RelationalEntityWriterUnitTests {
|
||||
DbActionTestSupport::extractPath, //
|
||||
DbActionTestSupport::actualEntityType, //
|
||||
DbActionTestSupport::isWithDependsOn) //
|
||||
.containsExactly(
|
||||
.containsExactly( //
|
||||
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), //
|
||||
tuple(Delete.class, Element.class, "other", null, false) //
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user