From c907eb1d35a5a5aa4e8838253c3d5c09029822aa Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 21 Feb 2020 11:56:18 +0100 Subject: [PATCH] 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. --- ...RepositoryConcurrencyIntegrationTests.java | 124 +++++++++++------- .../RelationalEntityWriterUnitTests.java | 2 +- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java index c1346a04..71520eb1 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java @@ -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 concurrencyEntities = createEntityStates(entity); + + TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager); + + List 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 createEntityStates(DummyEntity entity) { + List concurrencyEntities = new ArrayList<>(); Element element1 = new Element(null, 1L); Element element2 = new Element(null, 2L); for (int i = 0; i < 100; i++) { - List 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 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 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 { - } + interface DummyEntityRepository extends CrudRepository {} @Getter @AllArgsConstructor static class DummyEntity { - @Id - private Long id; - @With - String name; - @With - final List content; + @Id private Long id; + @With String name; + @With final List content; } diff --git a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java index 0fe9d721..15d07c41 100644 --- a/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java +++ b/spring-data-relational/src/test/java/org/springframework/data/relational/core/conversion/RelationalEntityWriterUnitTests.java @@ -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) // );