From 55c901b43bdac22ed5744d707b958b7c16ed12aa Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 20 Aug 2018 13:08:17 +0200 Subject: [PATCH] DATAJDBC-251 - Back-reference for dependent entity gets properly set. If a parent DbAction does not offer a generated id, the id of the entity is used instead. This behavior was broken by DATAJDBC-241. Related tickets: DATAJDBC-241. Original pull request: #85. --- .../jdbc/core/DefaultJdbcInterpreter.java | 7 +- .../core/DefaultJdbcInterpreterUnitTests.java | 37 ++- ...anuallyAssignedIdHsqlIntegrationTests.java | 253 ++++++++++++++++++ ...llyAssignedIdHsqlIntegrationTests-hsql.sql | 2 + 4 files changed, 292 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.java create mode 100644 src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests-hsql.sql diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java index dbcef36b5..48884b279 100644 --- a/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java +++ b/src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java @@ -171,7 +171,12 @@ class DefaultJdbcInterpreter implements Interpreter { Object entity = dependingOn.getEntity(); if (dependingOn instanceof DbAction.WithGeneratedId) { - return ((DbAction.WithGeneratedId) dependingOn).getGeneratedId(); + + Object generatedId = ((DbAction.WithGeneratedId) dependingOn).getGeneratedId(); + + if (generatedId != null) { + return generatedId; + } } return persistentEntity.getIdentifierAccessor(entity).getIdentifier(); diff --git a/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java index 3db867d29..022d53c88 100644 --- a/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java +++ b/src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java @@ -51,19 +51,44 @@ public class DefaultJdbcInterpreterUnitTests { DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class); DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy); + Container container = new Container(); + Element element = new Element(); + + InsertRoot containerInsert = new InsertRoot<>(container); + Insert insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context), + containerInsert); + @Test // DATAJDBC-145 public void insertDoesHonourNamingStrategyForBackReference() { - Container container = new Container(); container.id = CONTAINER_ID; + containerInsert.setGeneratedId(CONTAINER_ID); - Element element = new Element(); + interpreter.interpret(insert); - InsertRoot containerInsert = new InsertRoot<>(container); - containerInsert.setGeneratedId(CONTAINER_ID); + ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + } + + @Test // DATAJDBC-251 + public void idOfParentGetsPassedOnAsAdditionalParameterIfNoIdGotGenerated() { - Insert insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context), - containerInsert); + container.id = CONTAINER_ID; + + interpreter.interpret(insert); + + ArgumentCaptor> argumentCaptor = ArgumentCaptor.forClass(Map.class); + verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture()); + + assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID)); + } + + @Test // DATAJDBC-251 + public void generatedIdOfParentGetsPassedOnAsAdditionalParameter() { + + containerInsert.setGeneratedId(CONTAINER_ID); interpreter.interpret(insert); diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.java new file mode 100644 index 000000000..312bade97 --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.java @@ -0,0 +1,253 @@ +/* + * Copyright 2017-2018 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 + * + * http://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 static org.assertj.core.api.Assertions.*; + +import junit.framework.AssertionFailedError; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +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.relational.core.mapping.event.BeforeSaveEvent; +import org.springframework.data.repository.CrudRepository; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +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.annotation.Transactional; + +/** + * Very simple use cases for creation and usage of JdbcRepositories. + * + * @author Jens Schauder + */ +@ContextConfiguration +@Transactional +public class JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests { + + static AtomicLong id = new AtomicLong(0); + + @Configuration + @Import(TestConfiguration.class) + static class Config { + + @Autowired JdbcRepositoryFactory factory; + + @Bean + Class testClass() { + return JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.class; + } + + @Bean + DummyEntityRepository dummyEntityRepository() { + return factory.getRepository(DummyEntityRepository.class); + } + + @Bean + public ApplicationListener idSetting() { + + return (ApplicationListener) event -> { + + if (event.getEntity() instanceof DummyEntity) { + setIds((DummyEntity) event.getEntity()); + } + }; + } + + private void setIds(DummyEntity dummyEntity) { + + if (dummyEntity.getId() == null) { + dummyEntity.setId(id.incrementAndGet()); + } + + } + } + + @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); + @Rule public SpringMethodRule methodRule = new SpringMethodRule(); + + @Autowired NamedParameterJdbcTemplate template; + @Autowired DummyEntityRepository repository; + + @Test // DATAJDBC-113 + public void saveAndLoadEmptySet() { + + DummyEntity entity = repository.save(createDummyEntity()); + + assertThat(entity.id).isNotNull(); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + assertThat(reloaded.content) // + .isNotNull() // + .isEmpty(); + } + + @Test // DATAJDBC-113 + public void saveAndLoadNonEmptySet() { + + Element element1 = new Element(); + Element element2 = new Element(); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(element -> element.id != null); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + assertThat(reloaded.content) // + .isNotNull() // + .extracting(e -> e.id) // + .containsExactlyInAnyOrder(element1.id, element2.id); + } + + @Test // DATAJDBC-113 + public void findAllLoadsCollection() { + + Element element1 = new Element(); + Element element2 = new Element(); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(element -> element.id != null); + + Iterable reloaded = repository.findAll(); + + assertThat(reloaded) // + .extracting(e -> e.id, e -> e.content.size()) // + .containsExactly(tuple(entity.id, entity.content.size())); + } + + @Test // DATAJDBC-113 + public void updateSet() { + + Element element1 = createElement("one"); + Element element2 = createElement("two"); + Element element3 = createElement("three"); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + entity.content.remove(element1); + element2.content = "two changed"; + entity.content.add(element3); + + entity = repository.save(entity); + + assertThat(entity.id).isNotNull(); + assertThat(entity.content).allMatch(element -> element.id != null); + + DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new); + + // the elements got properly updated and reloaded + assertThat(reloaded.content) // + .isNotNull() // + .extracting(e -> e.id, e -> e.content) // + .containsExactlyInAnyOrder( // + tuple(element2.id, "two changed"), // + tuple(element3.id, "three") // + ); + + Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class); + assertThat(count).isEqualTo(2); + } + + @Test // DATAJDBC-113 + public void deletingWithSet() { + + Element element1 = createElement("one"); + Element element2 = createElement("two"); + + DummyEntity entity = createDummyEntity(); + entity.content.add(element1); + entity.content.add(element2); + + entity = repository.save(entity); + + repository.deleteById(entity.id); + + assertThat(repository.findById(entity.id)).isEmpty(); + + Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class); + assertThat(count).isEqualTo(0); + } + + + + + private Element createElement(String content) { + + Element element = new Element(); + element.content = content; + return element; + } + + private static DummyEntity createDummyEntity() { + + DummyEntity entity = new DummyEntity(); + entity.setName("Entity Name"); + return entity; + } + + interface DummyEntityRepository extends CrudRepository {} + + @Data + static class DummyEntity { + + @Id private Long id; + String name; + Set content = new HashSet<>(); + + } + + @RequiredArgsConstructor + static class Element { + + @Id private Long id; + String content; + } + +} diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests-hsql.sql new file mode 100644 index 000000000..9740f11d9 --- /dev/null +++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests-hsql.sql @@ -0,0 +1,2 @@ +CREATE TABLE dummy_entity ( id BIGINT PRIMARY KEY, NAME VARCHAR(100)); +CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), dummy_entity BIGINT not null);