Do not create persistent properties for Map and Collection-like entities.

These types are expected to behave like maps and collections and should not carry properties.
The only exception are types implementing Streamable as Streamable can be used in domain types.

Resolves: #3056
Original Pull Request: #3059
This commit is contained in:
Mark Paluch
2024-03-07 09:32:56 +01:00
committed by Christoph Strobl
parent 5502fcf893
commit 0766f2e56b
2 changed files with 81 additions and 16 deletions

View File

@@ -64,6 +64,7 @@ import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.util.CustomCollections;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.NullableWrapperConverters;
import org.springframework.data.util.Optionals;
@@ -458,17 +459,19 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
persistentEntities.put(typeInformation, Optional.of(entity));
}
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
Map<String, PropertyDescriptor> descriptors = new HashMap<>();
if (shouldCreateProperties(userTypeInformation)) {
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
Map<String, PropertyDescriptor> descriptors = new HashMap<>();
for (PropertyDescriptor descriptor : pds) {
descriptors.put(descriptor.getName(), descriptor);
for (PropertyDescriptor descriptor : pds) {
descriptors.put(descriptor.getName(), descriptor);
}
PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
persistentPropertyCreator.addPropertiesForRemainingDescriptors();
}
PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
persistentPropertyCreator.addPropertiesForRemainingDescriptors();
entity.verify();
if (persistentPropertyAccessorFactory.isSupported(entity)) {
@@ -518,6 +521,35 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
*/
protected abstract P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder);
/**
* Whether to create the {@link PersistentProperty}s for the given {@link TypeInformation}.
*
* @param typeInformation must not be {@literal null}.
* @return {@literal true} properties should be created, {@literal false} otherwise
*/
protected boolean shouldCreateProperties(TypeInformation<?> typeInformation) {
Class<?> type = typeInformation.getType();
return !typeInformation.isMap() && !isCollectionLike(type);
}
/**
* In contrast to TypeInformation, we do not consider types implementing Streamable collection-like as domain types
* can implement that type.
*
* @param type must not be {@literal null}.
* @return
* @see TypeInformation#isCollectionLike()
*/
private static boolean isCollectionLike(Class<?> type) {
return type.isArray() //
|| Iterable.class.equals(type) //
|| Streamable.class.equals(type) //
|| Collection.class.isAssignableFrom(type) || CustomCollections.isCollection(type);
}
@Override
public void afterPropertiesSet() {
initialize();
@@ -575,6 +607,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
this.remainingDescriptors = remainingDescriptors;
}
@Override
public void doWith(Field field) {
String fieldName = field.getName();

View File

@@ -22,14 +22,7 @@ import static org.mockito.Mockito.*;
import groovy.lang.MetaClass;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.*;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
@@ -49,6 +42,7 @@ import org.springframework.data.mapping.ShadowingPropertyTypeWithCtor;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
/**
@@ -285,6 +279,20 @@ class AbstractMappingContextUnitTests {
.doesNotContain(List.class, ArrayList.class);
}
@Test // GH-2390
void doesNotCreatePropertiesForMapAndCollectionTypes() {
assertThat(context.getPersistentEntity(HashSet.class)).isEmpty();
assertThat(context.getPersistentEntity(HashMap.class)).isEmpty();
}
@Test // GH-2390
void createsPropertiesForStreamableAndIterableTypes() {
assertThat(context.getPersistentEntity(MyStreamable.class)).hasSize(1);
assertThat(context.getPersistentEntity(MyIterable.class)).hasSize(1);
}
@Test // GH-2390
void detectsEntityTypeEvenIfSimpleTypeHolderConsidersCollectionsSimple() {
@@ -534,4 +542,28 @@ class AbstractMappingContextUnitTests {
}
static class MyIterable implements Iterable<StreamComponent> {
String name;
@Override
public Iterator<StreamComponent> iterator() {
return Collections.emptyIterator();
}
}
static class MyStreamable implements Streamable<StreamComponent> {
String name;
@Override
public Iterator<StreamComponent> iterator() {
return Collections.emptyIterator();
}
}
record StreamComponent(String name) {
}
}