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:
Jens Schauder
2020-02-21 11:56:18 +01:00
parent 06529abf5a
commit c907eb1d35
2 changed files with 75 additions and 51 deletions

View File

@@ -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;
}

View File

@@ -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) //
);