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.
This commit is contained in:
committed by
Oliver Gierke
parent
d158cd062d
commit
55c901b43b
@@ -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();
|
||||
|
||||
@@ -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<Container> 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;
|
||||
|
||||
Element element = new Element();
|
||||
|
||||
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
|
||||
containerInsert.setGeneratedId(CONTAINER_ID);
|
||||
|
||||
Insert<?> insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context),
|
||||
containerInsert);
|
||||
interpreter.interpret(insert);
|
||||
|
||||
ArgumentCaptor<Map<String, Object>> 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() {
|
||||
|
||||
container.id = CONTAINER_ID;
|
||||
|
||||
interpreter.interpret(insert);
|
||||
|
||||
ArgumentCaptor<Map<String, Object>> 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);
|
||||
|
||||
|
||||
@@ -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<BeforeSaveEvent>) 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<DummyEntity> 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<DummyEntity, Long> {}
|
||||
|
||||
@Data
|
||||
static class DummyEntity {
|
||||
|
||||
@Id private Long id;
|
||||
String name;
|
||||
Set<Element> content = new HashSet<>();
|
||||
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
static class Element {
|
||||
|
||||
@Id private Long id;
|
||||
String content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user