Skip to content

Commit c907eb1

Browse files
committed
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.
1 parent 06529ab commit c907eb1

File tree

2 files changed

+75
-51
lines changed

2 files changed

+75
-51
lines changed

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java

Lines changed: 74 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.springframework.data.jdbc.repository;
217

318
import junit.framework.AssertionFailedError;
@@ -13,9 +28,12 @@
1328
import org.springframework.context.annotation.Import;
1429
import org.springframework.data.annotation.Id;
1530
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
31+
import org.springframework.data.jdbc.testing.DatabaseProfileValueSource;
1632
import org.springframework.data.jdbc.testing.TestConfiguration;
1733
import org.springframework.data.repository.CrudRepository;
1834
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
35+
import org.springframework.test.annotation.IfProfileValue;
36+
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
1937
import org.springframework.test.context.ActiveProfiles;
2038
import org.springframework.test.context.ContextConfiguration;
2139
import org.springframework.test.context.junit4.rules.SpringClassRule;
@@ -31,12 +49,16 @@
3149

3250
import static org.assertj.core.api.Assertions.assertThat;
3351

34-
/**
52+
/** Tests that highly concurrent update operations of an entity don't cause deadlocks.
53+
*
3554
* @author Myeonghyeon Lee
55+
* @author Jens Schauder
3656
*/
3757
@ContextConfiguration
38-
@ActiveProfiles("mysql")
58+
@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)
59+
@IfProfileValue(name = "current.database.is.not.mysql", value = "false")
3960
public class JdbcRepositoryConcurrencyIntegrationTests {
61+
4062
@Configuration
4163
@Import(TestConfiguration.class)
4264
static class Config {
@@ -54,80 +76,82 @@ DummyEntityRepository dummyEntityRepository() {
5476
}
5577
}
5678

57-
@ClassRule
58-
public static final SpringClassRule classRule = new SpringClassRule();
59-
@Rule
60-
public SpringMethodRule methodRule = new SpringMethodRule();
79+
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
80+
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
6181

62-
@Autowired
63-
NamedParameterJdbcTemplate template;
64-
@Autowired
65-
DummyEntityRepository repository;
66-
@Autowired
67-
PlatformTransactionManager transactionManager;
82+
@Autowired NamedParameterJdbcTemplate template;
83+
@Autowired DummyEntityRepository repository;
84+
@Autowired PlatformTransactionManager transactionManager;
6885

69-
@Test // DATAJDBC-488
86+
@Test // DATAJDBC-488
7087
public void updateConcurrencyWithEmptyReferences() throws Exception {
88+
7189
DummyEntity entity = createDummyEntity();
7290
entity = repository.save(entity);
7391

7492
assertThat(entity.getId()).isNotNull();
7593

76-
List<DummyEntity> concurrencyEntities = new ArrayList<>();
77-
Element element1 = new Element(null, 1L);
78-
Element element2 = new Element(null, 2L);
79-
80-
for (int i = 0; i < 100; i++) {
81-
List<Element> newContent = Arrays.asList(
82-
element1.withContent(element1.content + i + 2),
83-
element2.withContent(element2.content + i + 2)
84-
);
85-
86-
concurrencyEntities.add(entity
87-
.withName(entity.getName() + i)
88-
.withContent(newContent));
89-
}
94+
List<DummyEntity> concurrencyEntities = createEntityStates(entity);
9095

9196
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
9297

9398
List<Exception> exceptions = new CopyOnWriteArrayList<>();
94-
CountDownLatch countDownLatch = new CountDownLatch(concurrencyEntities.size());
95-
concurrencyEntities.stream()
96-
.map(e -> new Thread(() -> {
97-
countDownLatch.countDown();
98-
try {
99-
transactionTemplate.execute(status -> repository.save(e));
100-
} catch (Exception ex) {
101-
exceptions.add(ex);
102-
}
103-
}))
104-
.forEach(Thread::start);
105-
106-
countDownLatch.await();
107-
108-
Thread.sleep(1000);
99+
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size()); // latch for all threads to wait on.
100+
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size()); // latch for main thread to wait on until all threads are done.
101+
102+
concurrencyEntities.stream() //
103+
.map(e -> new Thread(() -> {
104+
105+
try {
106+
107+
startLatch.countDown();
108+
startLatch.await();
109+
110+
transactionTemplate.execute(status -> repository.save(e));
111+
} catch (Exception ex) {
112+
exceptions.add(ex);
113+
} finally {
114+
doneLatch.countDown();
115+
}
116+
})) //
117+
.forEach(Thread::start);
118+
119+
doneLatch.await();
120+
109121
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
110122
assertThat(reloaded.content).hasSize(2);
111123
assertThat(exceptions).isEmpty();
112124
}
113125

126+
private List<DummyEntity> createEntityStates(DummyEntity entity) {
127+
128+
List<DummyEntity> concurrencyEntities = new ArrayList<>();
129+
Element element1 = new Element(null, 1L);
130+
Element element2 = new Element(null, 2L);
131+
132+
for (int i = 0; i < 100; i++) {
133+
134+
List<Element> newContent = Arrays.asList(element1.withContent(element1.content + i + 2),
135+
element2.withContent(element2.content + i + 2));
136+
137+
concurrencyEntities.add(entity.withName(entity.getName() + i).withContent(newContent));
138+
}
139+
return concurrencyEntities;
140+
}
141+
114142
private static DummyEntity createDummyEntity() {
115143
return new DummyEntity(null, "Entity Name", new ArrayList<>());
116144
}
117145

118-
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
119-
}
146+
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
120147

121148
@Getter
122149
@AllArgsConstructor
123150
static class DummyEntity {
124151

125-
@Id
126-
private Long id;
127-
@With
128-
String name;
129-
@With
130-
final List<Element> content;
152+
@Id private Long id;
153+
@With String name;
154+
@With final List<Element> content;
131155

132156
}
133157

spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public void existingEntityGetsConvertedToDeletePlusUpdate() {
157157
DbActionTestSupport::extractPath, //
158158
DbActionTestSupport::actualEntityType, //
159159
DbActionTestSupport::isWithDependsOn) //
160-
.containsExactly(
160+
.containsExactly( //
161161
tuple(UpdateRoot.class, SingleReferenceEntity.class, "", SingleReferenceEntity.class, false), //
162162
tuple(Delete.class, Element.class, "other", null, false) //
163163
);

0 commit comments

Comments
 (0)