Browse Source

Filter delegated properties for Kotlin data classes.

We now filter delegated properties (such as lazy) from being managed as persistent properties.

Closes #3112
pull/3119/head
Mark Paluch 2 years ago
parent
commit
a115ee6a44
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 5
      src/main/antora/modules/ROOT/pages/object-mapping.adoc
  2. 60
      src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java
  3. 12
      src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java
  4. 7
      src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt

5
src/main/antora/modules/ROOT/pages/object-mapping.adoc

@ -314,7 +314,7 @@ Consider the following `data` class `Person`: @@ -314,7 +314,7 @@ Consider the following `data` class `Person`:
data class Person(val id: String, val name: String)
----
The class above compiles to a typical class with an explicit constructor.We can customize this class by adding another constructor and annotate it with `@PersistenceCreator` to indicate a constructor preference:
The class above compiles to a typical class with an explicit constructor. We can customize this class by adding another constructor and annotate it with `@PersistenceCreator` to indicate a constructor preference:
[source,kotlin]
----
@ -335,6 +335,9 @@ data class Person(var id: String, val name: String = "unknown") @@ -335,6 +335,9 @@ data class Person(var id: String, val name: String = "unknown")
Every time the `name` parameter is either not part of the result or its value is `null`, then the `name` defaults to `unknown`.
NOTE: Delegated properties are not supported with Spring Data. The mapping metadata filters delegated properties for Kotlin Data classes.
In all other cases you can exclude synthetic fields for delegated properties by annotating the property with `@delegate:org.springframework.data.annotation.Transient`.
[[property-population-of-kotlin-data-classes]]
=== Property population of Kotlin data classes

60
src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

@ -770,6 +770,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -770,6 +770,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
matches.add(new PropertyMatch("class", null));
matches.add(new PropertyMatch("this\\$.*", null));
matches.add(new PropertyMatch("metaClass", "groovy.lang.MetaClass"));
matches.add(new KotlinDataClassPropertyMatch(".*\\$delegate", null));
UNMAPPED_PROPERTIES = Streamable.of(matches);
}
@ -782,7 +783,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -782,7 +783,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
}
return UNMAPPED_PROPERTIES.stream()//
.noneMatch(it -> it.matches(field.getName(), field.getType()));
.noneMatch(it -> it.matches(field));
}
/**
@ -800,7 +801,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -800,7 +801,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
}
return UNMAPPED_PROPERTIES.stream()//
.noneMatch(it -> it.matches(property.getName(), property.getType()));
.noneMatch(it -> it.matches(property));
}
/**
@ -832,6 +833,26 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -832,6 +833,26 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
/**
* Returns whether the given {@link Field} matches the defined {@link PropertyMatch}.
*
* @param field must not be {@literal null}.
* @return
*/
public boolean matches(Field field) {
return matches(field.getName(), field.getType());
}
/**
* Returns whether the given {@link Property} matches the defined {@link PropertyMatch}.
*
* @param property must not be {@literal null}.
* @return
*/
public boolean matches(Property property) {
return matches(property.getName(), property.getType());
}
/**
* Returns whether the given field name and type matches the defined {@link PropertyMatch}.
*
* @param name must not be {@literal null}.
* @param type must not be {@literal null}.
* @return
@ -852,6 +873,41 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<? @@ -852,6 +873,41 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
return true;
}
}
/**
* Value object extension to {@link PropertyMatch} that matches for fields only for Kotlin data classes.
*
* @author Mark Paluch
* @since 3.3.2
*/
static class KotlinDataClassPropertyMatch extends PropertyMatch {
public KotlinDataClassPropertyMatch(@Nullable String namePattern, @Nullable String typeName) {
super(namePattern, typeName);
}
@Override
public boolean matches(Field field) {
if (!KotlinReflectionUtils.isDataClass(field.getDeclaringClass())) {
return false;
}
return super.matches(field);
}
@Override
public boolean matches(Property property) {
Field field = property.getField().orElse(null);
if (field == null || !KotlinReflectionUtils.isDataClass(field.getDeclaringClass())) {
return false;
}
return super.matches(property);
}
}
}
}

12
src/test/java/org/springframework/data/mapping/context/AbstractMappingContextUnitTests.java

@ -39,7 +39,9 @@ import org.springframework.data.mapping.ShadowedPropertyType; @@ -39,7 +39,9 @@ import org.springframework.data.mapping.ShadowedPropertyType;
import org.springframework.data.mapping.ShadowedPropertyTypeWithCtor;
import org.springframework.data.mapping.ShadowingPropertyType;
import org.springframework.data.mapping.ShadowingPropertyTypeWithCtor;
import org.springframework.data.mapping.model.AbstractPersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.DataClassWithLazy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.Streamable;
@ -179,6 +181,16 @@ class AbstractMappingContextUnitTests { @@ -179,6 +181,16 @@ class AbstractMappingContextUnitTests {
assertThat(context.getPersistentEntity(SimpleDataClass.class)).isNotNull();
}
@Test // GH-3112
void shouldIgnoreLazyFieldsForDataClasses() {
BasicPersistentEntity<Object, SamplePersistentProperty> entity = context
.getRequiredPersistentEntity(DataClassWithLazy.class);
List<String> propertyNames = Streamable.of(entity).map(AbstractPersistentProperty::getName).toList();
assertThat(propertyNames).containsOnly("amount", "currency");
}
@Test // DATACMNS-1171
void shouldNotCreateEntityForSyntheticKotlinClass() {
assertThat(context.getPersistentEntity(TypeCreatingSyntheticClass.class)).isNotNull();

7
src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt

@ -29,6 +29,13 @@ data class ExtendedDataClassKt(val id: Long, val name: String) { @@ -29,6 +29,13 @@ data class ExtendedDataClassKt(val id: Long, val name: String) {
}
}
data class DataClassWithLazy(
val amount: Int,
val currency: String,
) {
val foo by lazy { 123 }
}
data class SingleSettableProperty constructor(val id: Double = Math.random()) {
val version: Int? = null
}

Loading…
Cancel
Save