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.
This commit is contained in:
Jens Schauder
2018-05-24 12:18:27 +02:00
committed by Greg Turnquist
parent c616e006cf
commit 577d7643dd
12 changed files with 383 additions and 18 deletions

View File

@@ -285,13 +285,14 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
persistentEntity.doWithProperties((PropertyHandler<RelationalPersistentProperty>) property -> {
if (!property.isEntity()) {
Object value = propertyAccessor.getProperty(property);
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
if (property.isEntity()) {
return;
}
Object value = propertyAccessor.getProperty(property);
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
});
return parameters;

View File

@@ -99,7 +99,9 @@ public class EntityRowMapper<T> implements RowMapper<T> {
} else {
propertyAccessor.setProperty(property, readFrom(resultSet, property, ""));
final Object value = readFrom(resultSet, property, "");
propertyAccessor.setProperty(property, value);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 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.core.mapping;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.Nullable;
/**
* A reference to the aggregate root of a different aggregate.
*
* @param <T> the type of the referenced aggregate root.
* @param <ID> the type of the id of the referenced aggregate root.
*
* @author Jens Schauder
*
* @since 1.0
*/
public interface AggregateReference<T, ID> {
static <T, ID> AggregateReference<T, ID> to(ID id) {
return new IdOnlyAggregateReference<>(id);
}
/**
* @return the id of the referenced aggregate. May be {@code null}.
*/
@Nullable
ID getId();
/**
* An {@link AggregateReference} that only holds the id of the referenced aggregate root.
*
* Note that there is no check that a matching aggregate for this id actually exists.
*
* @param <T>
* @param <ID>
*/
@RequiredArgsConstructor
class IdOnlyAggregateReference<T, ID> implements AggregateReference<T, ID> {
private final ID id;
@Override
public ID getId() {
return id;
}
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.CustomConversions.StoreConversions;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -49,6 +50,7 @@ import org.springframework.util.ClassUtils;
* Conversion is configurable by providing a customized {@link CustomConversions}.
*
* @author Mark Paluch
* @author Jens Schauder
* @see MappingContext
* @see SimpleTypeHolder
* @see CustomConversions
@@ -157,6 +159,14 @@ public class BasicRelationalConverter implements RelationalConverter {
return conversionService.convert(value, type.getType());
}
if (AggregateReference.class.isAssignableFrom(type.getType())) {
TypeInformation<?> idType = type.getSuperTypeInformation(AggregateReference.class)
.getTypeArguments().get(1);
return AggregateReference.to(readValue(value, idType));
}
return getPotentiallyConvertedSimpleRead(value, type.getType());
}
@@ -172,6 +182,10 @@ public class BasicRelationalConverter implements RelationalConverter {
return null;
}
if (AggregateReference.class.isAssignableFrom(value.getClass())) {
return writeValue (((AggregateReference) value).getId(), type);
}
Class<?> rawType = type.getType();
RelationalPersistentEntity<?> persistentEntity = context.getPersistentEntity(value.getClass());

View File

@@ -23,6 +23,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
@@ -96,6 +98,16 @@ class BasicRelationalPersistentProperty extends AnnotationBasedPersistentPropert
throw new UnsupportedOperationException();
}
@Override
public boolean isEntity() {
return super.isEntity() && !isReference();
}
@Override
public boolean isReference() {
return AggregateReference.class.isAssignableFrom(getRawType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.mapping.model.JdbcPersistentProperty#getColumnName()
@@ -114,11 +126,20 @@ class BasicRelationalPersistentProperty extends AnnotationBasedPersistentPropert
@Override
public Class getColumnType() {
if (isReference()) {
return columnTypeForReference();
}
Class columnType = columnTypeIfEntity(getActualType());
return columnType == null ? columnTypeForNonEntity(getActualType()) : columnType;
}
@Override
public int getSqlType() {
return JdbcUtil.sqlTypeFor(getColumnType());
}
@Override
public RelationalPersistentEntity<?> getOwner() {
return (RelationalPersistentEntity<?>) super.getOwner();
@@ -178,4 +199,13 @@ class BasicRelationalPersistentProperty extends AnnotationBasedPersistentPropert
.findFirst() //
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
}
private Class columnTypeForReference() {
Class<?> componentType = getTypeInformation().getRequiredComponentType().getType();
RelationalPersistentEntity<?> referencedEntity = context.getRequiredPersistentEntity(componentType);
return referencedEntity.getRequiredIdProperty().getColumnType();
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.relational.core.mapping;
import lombok.Getter;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
@@ -78,4 +79,9 @@ public class RelationalMappingContext
RelationalPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
return new BasicRelationalPersistentProperty(property, owner, simpleTypeHolder, this);
}
@Override
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
return super.shouldCreatePersistentEntityFor(type) && !AggregateReference.class.isAssignableFrom(type.getType());
}
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.relational.core.mapping;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
@@ -26,6 +27,8 @@ import org.springframework.lang.Nullable;
*/
public interface RelationalPersistentProperty extends PersistentProperty<RelationalPersistentProperty> {
boolean isReference();
/**
* Returns the name of the column backing this property.
*
@@ -40,6 +43,14 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
*/
Class<?> getColumnType();
/**
* The SQL type constant used when using this property as a parameter for a SQL statement.
* @return Must not be {@code null}.
*
* @see java.sql.Types
*/
int getSqlType();
@Override
RelationalPersistentEntity<?> getOwner();