Skip to content

Commit 577d764

Browse files
schaudergregturn
authored andcommitted
DATAJDBC-221 - Added AggregateReference for references across aggregates.
AggregateReferences can be used to reference other aggregates via their aggregate root without making them part of the referencing aggregate. I.e. the referenced entities will not be included in SQL statements for the referencing aggregates, apart from their id. Conversion between AggregateReferences and their ids and vice versa is done by the RelationalConverter.
1 parent c616e00 commit 577d764

File tree

12 files changed

+382
-17
lines changed

12 files changed

+382
-17
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,13 +285,14 @@ private <S> MapSqlParameterSource getPropertyMap(final S instance, RelationalPer
285285

286286
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
287287

288-
if (!property.isEntity()) {
288+
if (property.isEntity()) {
289+
return;
290+
}
289291

290-
Object value = propertyAccessor.getProperty(property);
292+
Object value = propertyAccessor.getProperty(property);
293+
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
294+
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
291295

292-
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
293-
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
294-
}
295296
});
296297

297298
return parameters;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ private T populateProperties(T result, ResultSet resultSet) {
9999

100100
} else {
101101

102-
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
102+
final Object value = readFrom(resultSet, property, "");
103+
104+
propertyAccessor.setProperty(property, value);
103105
}
104106
}
105107

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 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.core.mapping;
17+
18+
import lombok.RequiredArgsConstructor;
19+
20+
import org.springframework.lang.Nullable;
21+
22+
/**
23+
* A reference to the aggregate root of a different aggregate.
24+
*
25+
* @param <T> the type of the referenced aggregate root.
26+
* @param <ID> the type of the id of the referenced aggregate root.
27+
*
28+
* @author Jens Schauder
29+
*
30+
* @since 1.0
31+
*/
32+
public interface AggregateReference<T, ID> {
33+
34+
static <T, ID> AggregateReference<T, ID> to(ID id) {
35+
return new IdOnlyAggregateReference<>(id);
36+
}
37+
38+
/**
39+
* @return the id of the referenced aggregate. May be {@code null}.
40+
*/
41+
@Nullable
42+
ID getId();
43+
44+
/**
45+
* An {@link AggregateReference} that only holds the id of the referenced aggregate root.
46+
*
47+
* Note that there is no check that a matching aggregate for this id actually exists.
48+
*
49+
* @param <T>
50+
* @param <ID>
51+
*/
52+
@RequiredArgsConstructor
53+
class IdOnlyAggregateReference<T, ID> implements AggregateReference<T, ID> {
54+
55+
private final ID id;
56+
57+
@Override
58+
public ID getId() {
59+
return id;
60+
}
61+
}
62+
63+
}

src/main/java/org/springframework/data/relational/core/conversion/BasicRelationalConverter.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.convert.CustomConversions;
2828
import org.springframework.data.convert.CustomConversions.StoreConversions;
2929
import org.springframework.data.convert.EntityInstantiators;
30+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
3031
import org.springframework.data.mapping.PersistentEntity;
3132
import org.springframework.data.mapping.PersistentProperty;
3233
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -49,6 +50,7 @@
4950
* Conversion is configurable by providing a customized {@link CustomConversions}.
5051
*
5152
* @author Mark Paluch
53+
* @author Jens Schauder
5254
* @see MappingContext
5355
* @see SimpleTypeHolder
5456
* @see CustomConversions
@@ -157,6 +159,14 @@ public Object readValue(@Nullable Object value, TypeInformation<?> type) {
157159
return conversionService.convert(value, type.getType());
158160
}
159161

162+
if (AggregateReference.class.isAssignableFrom(type.getType())) {
163+
164+
TypeInformation<?> idType = type.getSuperTypeInformation(AggregateReference.class)
165+
.getTypeArguments().get(1);
166+
167+
return AggregateReference.to(readValue(value, idType));
168+
}
169+
160170
return getPotentiallyConvertedSimpleRead(value, type.getType());
161171
}
162172

@@ -172,6 +182,10 @@ public Object writeValue(@Nullable Object value, TypeInformation<?> type) {
172182
return null;
173183
}
174184

185+
if (AggregateReference.class.isAssignableFrom(value.getClass())) {
186+
return writeValue (((AggregateReference) value).getId(), type);
187+
}
188+
175189
Class<?> rawType = type.getType();
176190
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());
177191

src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.Optional;
2424
import java.util.Set;
2525

26+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
27+
import org.springframework.data.jdbc.support.JdbcUtil;
2628
import org.springframework.data.mapping.Association;
2729
import org.springframework.data.mapping.PersistentEntity;
2830
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
@@ -96,6 +98,16 @@ protected Association<RelationalPersistentProperty> createAssociation() {
9698
throw new UnsupportedOperationException();
9799
}
98100

101+
@Override
102+
public boolean isEntity() {
103+
return super.isEntity() && !isReference();
104+
}
105+
106+
@Override
107+
public boolean isReference() {
108+
return AggregateReference.class.isAssignableFrom(getRawType());
109+
}
110+
99111
/*
100112
* (non-Javadoc)
101113
* @see org.springframework.data.jdbc.core.mapping.model.JdbcPersistentProperty#getColumnName()
@@ -114,11 +126,20 @@ public String getColumnName() {
114126
@Override
115127
public Class getColumnType() {
116128

129+
if (isReference()) {
130+
return columnTypeForReference();
131+
}
132+
117133
Class columnType = columnTypeIfEntity(getActualType());
118134

119135
return columnType == null ? columnTypeForNonEntity(getActualType()) : columnType;
120136
}
121137

138+
@Override
139+
public int getSqlType() {
140+
return JdbcUtil.sqlTypeFor(getColumnType());
141+
}
142+
122143
@Override
123144
public RelationalPersistentEntity<?> getOwner() {
124145
return (RelationalPersistentEntity<?>) super.getOwner();
@@ -178,4 +199,13 @@ private Class columnTypeForNonEntity(Class type) {
178199
.findFirst() //
179200
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
180201
}
202+
203+
private Class columnTypeForReference() {
204+
205+
Class<?> componentType = getTypeInformation().getRequiredComponentType().getType();
206+
RelationalPersistentEntity<?> referencedEntity = context.getRequiredPersistentEntity(componentType);
207+
208+
return referencedEntity.getRequiredIdProperty().getColumnType();
209+
}
210+
181211
}

src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import lombok.Getter;
1919

20+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
2021
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
2122
import org.springframework.data.mapping.context.AbstractMappingContext;
2223
import org.springframework.data.mapping.context.MappingContext;
@@ -78,4 +79,9 @@ protected RelationalPersistentProperty createPersistentProperty(Property propert
7879
RelationalPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
7980
return new BasicRelationalPersistentProperty(property, owner, simpleTypeHolder, this);
8081
}
82+
83+
@Override
84+
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
85+
return super.shouldCreatePersistentEntityFor(type) && !AggregateReference.class.isAssignableFrom(type.getType());
86+
}
8187
}

src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.relational.core.mapping;
1717

18+
import org.springframework.core.convert.TypeDescriptor;
1819
import org.springframework.data.mapping.PersistentProperty;
1920
import org.springframework.lang.Nullable;
2021

@@ -26,6 +27,8 @@
2627
*/
2728
public interface RelationalPersistentProperty extends PersistentProperty<RelationalPersistentProperty> {
2829

30+
boolean isReference();
31+
2932
/**
3033
* Returns the name of the column backing this property.
3134
*
@@ -40,6 +43,14 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
4043
*/
4144
Class<?> getColumnType();
4245

46+
/**
47+
* The SQL type constant used when using this property as a parameter for a SQL statement.
48+
* @return Must not be {@code null}.
49+
*
50+
* @see java.sql.Types
51+
*/
52+
int getSqlType();
53+
4354
@Override
4455
RelationalPersistentEntity<?> getOwner();
4556

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.junit.Before;
2626
import org.junit.Test;
2727
import org.springframework.data.annotation.Id;
28+
import org.springframework.data.jdbc.core.mapping.AggregateReference;
2829
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
2930
import org.springframework.data.mapping.PersistentPropertyPath;
3031
import org.springframework.data.relational.core.mapping.NamingStrategy;
@@ -68,6 +69,7 @@ public void findOne() {
6869
.startsWith("SELECT") //
6970
.contains("dummy_entity.x_id AS x_id,") //
7071
.contains("dummy_entity.x_name AS x_name,") //
72+
.contains("dummy_entity.x_other AS x_other,") //
7173
.contains("ref.x_l1id AS ref_x_l1id") //
7274
.contains("ref.x_content AS ref_x_content").contains(" FROM dummy_entity") //
7375
// 1-N relationships do not get loaded via join
@@ -139,9 +141,10 @@ public void findAllByProperty() {
139141
// this would get called when DummyEntity is the element type of a Set
140142
String sql = sqlGenerator.getFindAllByProperty("back-ref", null, false);
141143

142-
assertThat(sql).isEqualTo("SELECT dummy_entity.x_id AS x_id, dummy_entity.x_name AS x_name, "
143-
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further "
144-
+ "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.x_id "
144+
assertThat(sql).isEqualTo("SELECT dummy_entity.x_id AS x_id, dummy_entity.x_name AS x_name, " //
145+
+ "dummy_entity.x_other AS x_other, " //
146+
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further " //
147+
+ "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.x_id " //
145148
+ "WHERE back-ref = :back-ref");
146149
}
147150

@@ -152,6 +155,7 @@ public void findAllByPropertyWithKey() {
152155
String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", false);
153156

154157
assertThat(sql).isEqualTo("SELECT dummy_entity.x_id AS x_id, dummy_entity.x_name AS x_name, " //
158+
+ "dummy_entity.x_other AS x_other, " //
155159
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " //
156160
+ "dummy_entity.key-column AS key-column " //
157161
+ "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.x_id " //
@@ -169,10 +173,11 @@ public void findAllByPropertyWithKeyOrdered() {
169173
// this would get called when DummyEntity is th element type of a Map
170174
String sql = sqlGenerator.getFindAllByProperty("back-ref", "key-column", true);
171175

172-
assertThat(sql).isEqualTo("SELECT dummy_entity.x_id AS x_id, dummy_entity.x_name AS x_name, "
173-
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, "
174-
+ "dummy_entity.key-column AS key-column "
175-
+ "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.x_id "
176+
assertThat(sql).isEqualTo("SELECT dummy_entity.x_id AS x_id, dummy_entity.x_name AS x_name, " //
177+
+ "dummy_entity.x_other AS x_other, " //
178+
+ "ref.x_l1id AS ref_x_l1id, ref.x_content AS ref_x_content, ref.x_further AS ref_x_further, " //
179+
+ "dummy_entity.key-column AS key-column " //
180+
+ "FROM dummy_entity LEFT OUTER JOIN referenced_entity AS ref ON ref.dummy_entity = dummy_entity.x_id " //
176181
+ "WHERE back-ref = :back-ref " + "ORDER BY key-column");
177182
}
178183

@@ -222,6 +227,7 @@ static class DummyEntity {
222227
ReferencedEntity ref;
223228
Set<Element> elements;
224229
Map<Integer, Element> mappedElements;
230+
AggregateReference<OtherAggregate, Long> other;
225231
}
226232

227233
@SuppressWarnings("unused")
@@ -250,7 +256,11 @@ static class ParentOfNoIdChild {
250256
}
251257

252258
static class NoIdChild {
259+
}
253260

261+
static class OtherAggregate {
262+
@Id Long id;
263+
String name;
254264
}
255265

256266
private static class PrefixingNamingStrategy implements NamingStrategy {

0 commit comments

Comments
 (0)