Skip to content

Commit 55c901b

Browse files
schauderodrotbohm
authored andcommitted
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.
1 parent d158cd0 commit 55c901b

File tree

4 files changed

+292
-7
lines changed

4 files changed

+292
-7
lines changed

src/main/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreter.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,12 @@ private Object getIdFromEntityDependingOn(DbAction.WithEntity<?> dependingOn,
171171
Object entity = dependingOn.getEntity();
172172

173173
if (dependingOn instanceof DbAction.WithGeneratedId) {
174-
return ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
174+
175+
Object generatedId = ((DbAction.WithGeneratedId<?>) dependingOn).getGeneratedId();
176+
177+
if (generatedId != null) {
178+
return generatedId;
179+
}
175180
}
176181

177182
return persistentEntity.getIdentifierAccessor(entity).getIdentifier();

src/test/java/org/springframework/data/jdbc/core/DefaultJdbcInterpreterUnitTests.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,44 @@ public String getReverseColumnName(RelationalPersistentProperty property) {
5151
DataAccessStrategy dataAccessStrategy = mock(DataAccessStrategy.class);
5252
DefaultJdbcInterpreter interpreter = new DefaultJdbcInterpreter(context, dataAccessStrategy);
5353

54+
Container container = new Container();
55+
Element element = new Element();
56+
57+
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
58+
Insert<?> insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context),
59+
containerInsert);
60+
5461
@Test // DATAJDBC-145
5562
public void insertDoesHonourNamingStrategyForBackReference() {
5663

57-
Container container = new Container();
5864
container.id = CONTAINER_ID;
65+
containerInsert.setGeneratedId(CONTAINER_ID);
5966

60-
Element element = new Element();
67+
interpreter.interpret(insert);
6168

62-
InsertRoot<Container> containerInsert = new InsertRoot<>(container);
63-
containerInsert.setGeneratedId(CONTAINER_ID);
69+
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
70+
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
71+
72+
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
73+
}
74+
75+
@Test // DATAJDBC-251
76+
public void idOfParentGetsPassedOnAsAdditionalParameterIfNoIdGotGenerated() {
6477

65-
Insert<?> insert = new Insert<>(element, PropertyPathUtils.toPath("element", Container.class, context),
66-
containerInsert);
78+
container.id = CONTAINER_ID;
79+
80+
interpreter.interpret(insert);
81+
82+
ArgumentCaptor<Map<String, Object>> argumentCaptor = ArgumentCaptor.forClass(Map.class);
83+
verify(dataAccessStrategy).insert(eq(element), eq(Element.class), argumentCaptor.capture());
84+
85+
assertThat(argumentCaptor.getValue()).containsExactly(new SimpleEntry(BACK_REFERENCE, CONTAINER_ID));
86+
}
87+
88+
@Test // DATAJDBC-251
89+
public void generatedIdOfParentGetsPassedOnAsAdditionalParameter() {
90+
91+
containerInsert.setGeneratedId(CONTAINER_ID);
6792

6893
interpreter.interpret(insert);
6994

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright 2017-2018 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+
* http://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+
*/
16+
package org.springframework.data.jdbc.repository;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import junit.framework.AssertionFailedError;
21+
import lombok.Data;
22+
import lombok.RequiredArgsConstructor;
23+
24+
import java.util.HashMap;
25+
import java.util.HashSet;
26+
import java.util.Set;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
import java.util.concurrent.atomic.AtomicLong;
29+
30+
import org.junit.ClassRule;
31+
import org.junit.Rule;
32+
import org.junit.Test;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.context.ApplicationListener;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.context.annotation.Import;
38+
import org.springframework.data.annotation.Id;
39+
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
40+
import org.springframework.data.jdbc.testing.TestConfiguration;
41+
import org.springframework.data.relational.core.mapping.event.BeforeSaveEvent;
42+
import org.springframework.data.repository.CrudRepository;
43+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
44+
import org.springframework.test.context.ContextConfiguration;
45+
import org.springframework.test.context.junit4.rules.SpringClassRule;
46+
import org.springframework.test.context.junit4.rules.SpringMethodRule;
47+
import org.springframework.transaction.annotation.Transactional;
48+
49+
/**
50+
* Very simple use cases for creation and usage of JdbcRepositories.
51+
*
52+
* @author Jens Schauder
53+
*/
54+
@ContextConfiguration
55+
@Transactional
56+
public class JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests {
57+
58+
static AtomicLong id = new AtomicLong(0);
59+
60+
@Configuration
61+
@Import(TestConfiguration.class)
62+
static class Config {
63+
64+
@Autowired JdbcRepositoryFactory factory;
65+
66+
@Bean
67+
Class<?> testClass() {
68+
return JdbcRepositoryWithCollectionsAndManuallyAssignedIdHsqlIntegrationTests.class;
69+
}
70+
71+
@Bean
72+
DummyEntityRepository dummyEntityRepository() {
73+
return factory.getRepository(DummyEntityRepository.class);
74+
}
75+
76+
@Bean
77+
public ApplicationListener<?> idSetting() {
78+
79+
return (ApplicationListener<BeforeSaveEvent>) event -> {
80+
81+
if (event.getEntity() instanceof DummyEntity) {
82+
setIds((DummyEntity) event.getEntity());
83+
}
84+
};
85+
}
86+
87+
private void setIds(DummyEntity dummyEntity) {
88+
89+
if (dummyEntity.getId() == null) {
90+
dummyEntity.setId(id.incrementAndGet());
91+
}
92+
93+
}
94+
}
95+
96+
@ClassRule public static final SpringClassRule classRule = new SpringClassRule();
97+
@Rule public SpringMethodRule methodRule = new SpringMethodRule();
98+
99+
@Autowired NamedParameterJdbcTemplate template;
100+
@Autowired DummyEntityRepository repository;
101+
102+
@Test // DATAJDBC-113
103+
public void saveAndLoadEmptySet() {
104+
105+
DummyEntity entity = repository.save(createDummyEntity());
106+
107+
assertThat(entity.id).isNotNull();
108+
109+
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
110+
111+
assertThat(reloaded.content) //
112+
.isNotNull() //
113+
.isEmpty();
114+
}
115+
116+
@Test // DATAJDBC-113
117+
public void saveAndLoadNonEmptySet() {
118+
119+
Element element1 = new Element();
120+
Element element2 = new Element();
121+
122+
DummyEntity entity = createDummyEntity();
123+
entity.content.add(element1);
124+
entity.content.add(element2);
125+
126+
entity = repository.save(entity);
127+
128+
assertThat(entity.id).isNotNull();
129+
assertThat(entity.content).allMatch(element -> element.id != null);
130+
131+
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
132+
133+
assertThat(reloaded.content) //
134+
.isNotNull() //
135+
.extracting(e -> e.id) //
136+
.containsExactlyInAnyOrder(element1.id, element2.id);
137+
}
138+
139+
@Test // DATAJDBC-113
140+
public void findAllLoadsCollection() {
141+
142+
Element element1 = new Element();
143+
Element element2 = new Element();
144+
145+
DummyEntity entity = createDummyEntity();
146+
entity.content.add(element1);
147+
entity.content.add(element2);
148+
149+
entity = repository.save(entity);
150+
151+
assertThat(entity.id).isNotNull();
152+
assertThat(entity.content).allMatch(element -> element.id != null);
153+
154+
Iterable<DummyEntity> reloaded = repository.findAll();
155+
156+
assertThat(reloaded) //
157+
.extracting(e -> e.id, e -> e.content.size()) //
158+
.containsExactly(tuple(entity.id, entity.content.size()));
159+
}
160+
161+
@Test // DATAJDBC-113
162+
public void updateSet() {
163+
164+
Element element1 = createElement("one");
165+
Element element2 = createElement("two");
166+
Element element3 = createElement("three");
167+
168+
DummyEntity entity = createDummyEntity();
169+
entity.content.add(element1);
170+
entity.content.add(element2);
171+
172+
entity = repository.save(entity);
173+
174+
entity.content.remove(element1);
175+
element2.content = "two changed";
176+
entity.content.add(element3);
177+
178+
entity = repository.save(entity);
179+
180+
assertThat(entity.id).isNotNull();
181+
assertThat(entity.content).allMatch(element -> element.id != null);
182+
183+
DummyEntity reloaded = repository.findById(entity.id).orElseThrow(AssertionFailedError::new);
184+
185+
// the elements got properly updated and reloaded
186+
assertThat(reloaded.content) //
187+
.isNotNull() //
188+
.extracting(e -> e.id, e -> e.content) //
189+
.containsExactlyInAnyOrder( //
190+
tuple(element2.id, "two changed"), //
191+
tuple(element3.id, "three") //
192+
);
193+
194+
Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class);
195+
assertThat(count).isEqualTo(2);
196+
}
197+
198+
@Test // DATAJDBC-113
199+
public void deletingWithSet() {
200+
201+
Element element1 = createElement("one");
202+
Element element2 = createElement("two");
203+
204+
DummyEntity entity = createDummyEntity();
205+
entity.content.add(element1);
206+
entity.content.add(element2);
207+
208+
entity = repository.save(entity);
209+
210+
repository.deleteById(entity.id);
211+
212+
assertThat(repository.findById(entity.id)).isEmpty();
213+
214+
Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class);
215+
assertThat(count).isEqualTo(0);
216+
}
217+
218+
219+
220+
221+
private Element createElement(String content) {
222+
223+
Element element = new Element();
224+
element.content = content;
225+
return element;
226+
}
227+
228+
private static DummyEntity createDummyEntity() {
229+
230+
DummyEntity entity = new DummyEntity();
231+
entity.setName("Entity Name");
232+
return entity;
233+
}
234+
235+
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
236+
237+
@Data
238+
static class DummyEntity {
239+
240+
@Id private Long id;
241+
String name;
242+
Set<Element> content = new HashSet<>();
243+
244+
}
245+
246+
@RequiredArgsConstructor
247+
static class Element {
248+
249+
@Id private Long id;
250+
String content;
251+
}
252+
253+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CREATE TABLE dummy_entity ( id BIGINT PRIMARY KEY, NAME VARCHAR(100));
2+
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, content VARCHAR(100), dummy_entity BIGINT not null);

0 commit comments

Comments
 (0)