Skip to content

Commit

Permalink
Filter delegated properties for Kotlin data classes.
Browse files Browse the repository at this point in the history
We now filter delegated properties (such as lazy) from being managed as persistent properties.

Closes #3112
  • Loading branch information
mp911de committed Jun 24, 2024
1 parent 8f08d60 commit 05dcd72
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 3 deletions.
5 changes: 4 additions & 1 deletion src/main/antora/modules/ROOT/pages/object-mapping.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
----
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ static enum PersistentPropertyFilter implements FieldFilter {
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);
}
Expand All @@ -782,7 +783,7 @@ public boolean matches(Field field) {
}

return UNMAPPED_PROPERTIES.stream()//
.noneMatch(it -> it.matches(field.getName(), field.getType()));
.noneMatch(it -> it.matches(field));
}

/**
Expand All @@ -800,7 +801,7 @@ public boolean matches(Property property) {
}

return UNMAPPED_PROPERTIES.stream()//
.noneMatch(it -> it.matches(property.getName(), property.getType()));
.noneMatch(it -> it.matches(property));
}

/**
Expand Down Expand Up @@ -832,6 +833,26 @@ public PropertyMatch(@Nullable String namePattern, @Nullable String typeName) {
/**
* 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
Expand All @@ -852,6 +873,41 @@ public boolean matches(String name, Class<?> type) {
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);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
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;
Expand Down Expand Up @@ -179,6 +181,16 @@ void shouldCreateEntityForKotlinDataClass() {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 05dcd72

Please sign in to comment.