Skip to content

Commit

Permalink
Include transient properties in persistent entity metamodel.
Browse files Browse the repository at this point in the history
  • Loading branch information
mp911de committed Nov 4, 2024
1 parent f2ecc51 commit 26f6ff3
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
* Returns the {@link PreferredConstructor} to be used to instantiate objects of this {@link PersistentEntity}.
*
* @return {@literal null} in case no suitable constructor for automatic construction can be found. This usually
* indicates that the instantiation of the object of that persistent entity is done through either a
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom
* conversion mechanisms entirely.
* indicates that the instantiation of the object of that persistent entity is done through either a customer
* {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion
* mechanisms entirely.
* @deprecated since 3.0, use {@link #getInstanceCreatorMetadata()}.
*/
@Nullable
Expand All @@ -61,8 +61,8 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
*
* @return {@literal null} in case no suitable creation mechanism for automatic construction can be found. This
* usually indicates that the instantiation of the object of that persistent entity is done through either a
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom
* conversion mechanisms entirely.
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion
* mechanisms entirely.
* @since 3.0
*/
@Nullable
Expand Down Expand Up @@ -136,17 +136,17 @@ default P getRequiredIdProperty() {
}

/**
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property
* is available on the entity.
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property is
* available on the entity.
*
* @return the version property of the {@link PersistentEntity}.
*/
@Nullable
P getVersionProperty();

/**
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property
* is available on the entity.
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property is
* available on the entity.
*
* @return the version property of the {@link PersistentEntity}.
* @throws IllegalStateException if {@link PersistentEntity} does not define a {@literal version} property.
Expand All @@ -166,7 +166,7 @@ default P getRequiredVersionProperty() {
/**
* Obtains a {@link PersistentProperty} instance by name.
*
* @param name The name of the property. Can be {@literal null}.
* @param name the name of the property. Can be {@literal null}.
* @return the {@link PersistentProperty} or {@literal null} if it doesn't exist.
*/
@Nullable
Expand Down Expand Up @@ -213,6 +213,28 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
*/
Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType);

/**
* Obtains a transient {@link PersistentProperty} instance by name. You can check with {@link #isTransient(String)}
* whether there is a transient property before calling this method.
*
* @param name the name of the property. Can be {@literal null}.
* @return the {@link PersistentProperty} or {@literal null} if it doesn't exist.
* @since 3.3
* @see #isTransient(String)
*/
@Nullable
P getTransientProperty(String name);

/**
* Returns whether the property is transient.
*
* @param property name of the property.
* @return {@code true} if the property is transient. Applies only for existing properties. {@code false} if the
* property does not exist or is not transient.
* @since 3.3
*/
boolean isTransient(String property);

/**
* Returns whether the {@link PersistentEntity} has an id property. If this call returns {@literal true},
* {@link #getIdProperty()} will return a non-{@literal null} value.
Expand All @@ -237,8 +259,8 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
Class<T> getType();

/**
* Returns the alias to be used when storing type information. Might be {@literal null} to indicate that there was
* no alias defined through the mapping metadata.
* Returns the alias to be used when storing type information. Might be {@literal null} to indicate that there was no
* alias defined through the mapping metadata.
*
* @return
*/
Expand Down Expand Up @@ -268,8 +290,8 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
void doWithProperties(SimplePropertyHandler handler);

/**
* Applies the given {@link AssociationHandler} to all {@link Association} contained in this
* {@link PersistentEntity}. The iteration order is undefined.
* Applies the given {@link AssociationHandler} to all {@link Association} contained in this {@link PersistentEntity}.
* The iteration order is undefined.
*
* @param handler must not be {@literal null}.
*/
Expand All @@ -284,8 +306,8 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
void doWithAssociations(SimpleAssociationHandler handler);

/**
* Applies the given {@link PropertyHandler} to both all {@link PersistentProperty}s as well as all inverse
* properties of all {@link Association}s. The iteration order is undefined.
* Applies the given {@link PropertyHandler} to both all {@link PersistentProperty}s as well as all inverse properties
* of all {@link Association}s. The iteration order is undefined.
*
* @param handler must not be {@literal null}.
* @since 2.5
Expand Down Expand Up @@ -370,7 +392,7 @@ default <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType)
*
* @param bean must not be {@literal null}.
* @throws IllegalArgumentException in case the given bean is not an instance of the typ represented by the
* {@link PersistentEntity}.
* {@link PersistentEntity}.
* @return whether the given bean is considered a new instance.
*/
boolean isNew(Object bean);
Expand All @@ -386,8 +408,8 @@ default <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType)
boolean isImmutable();

/**
* Returns whether the entity needs properties to be populated, i.e. if any property exists that's not initialized
* by the constructor.
* Returns whether the entity needs properties to be populated, i.e. if any property exists that's not initialized by
* the constructor.
*
* @return
* @since 2.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,10 +643,6 @@ private void createAndRegisterProperty(Property input) {

P property = createPersistentProperty(input, entity, simpleTypeHolder);

if (property.isTransient()) {
return;
}

if (!input.isFieldBacked() && !property.usePropertyAccess()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
private final @Nullable InstanceCreatorMetadata<P> creator;
private final TypeInformation<T> information;
private final List<P> properties;
private final List<P> transientProperties;
private final List<P> persistentPropertiesCache;
private final @Nullable Comparator<P> comparator;
private final Set<Association<P>> associations;

private final Map<String, P> propertyCache;

private final Map<String, P> transientPropertyCache;
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
private final MultiValueMap<Class<? extends Annotation>, P> propertyAnnotationCache;

Expand Down Expand Up @@ -110,12 +113,14 @@ public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparato

this.information = information;
this.properties = new ArrayList<>();
this.transientProperties = new ArrayList<>(0);
this.persistentPropertiesCache = new ArrayList<>();
this.comparator = comparator;
this.creator = InstanceCreatorMetadataDiscoverer.discover(this);
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));

this.propertyCache = new HashMap<>(16, 1.0f);
this.transientPropertyCache = new HashMap<>(0, 1f);
this.annotationCache = new ConcurrentHashMap<>(16);
this.propertyAnnotationCache = CollectionUtils
.toMultiValueMap(new ConcurrentHashMap<>(16));
Expand Down Expand Up @@ -190,6 +195,18 @@ public void addPersistentProperty(P property) {

Assert.notNull(property, "Property must not be null");

if (property.isTransient()) {

if (transientProperties.contains(property)) {
return;
}

transientProperties.add(property);
transientPropertyCache.put(property.getName(), property);

return;
}

if (properties.contains(property)) {
return;
}
Expand Down Expand Up @@ -274,6 +291,19 @@ public P getPersistentProperty(String name) {
return propertyCache.get(name);
}

@Override
public P getTransientProperty(String name) {
return transientPropertyCache.get(name);
}

@Override
public boolean isTransient(String property) {

P transientProperty = getTransientProperty(property);

return transientProperty != null && transientProperty.isTransient();
}

@Override
public Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.mapping.model;

import org.springframework.data.annotation.Transient;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.Parameter;
Expand Down Expand Up @@ -56,6 +57,13 @@ public <T> T getParameterValue(Parameter<T, P> parameter) {
return (T) parent;
}

if (parameter.getAnnotations().isPresent(Transient.class)) {

// parameter.getRawType().isPrimitive()
return null;

}

String name = parameter.getName();

if (name == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
Expand Down Expand Up @@ -105,8 +107,7 @@ void returnsTypeAliasIfAnnotated() {
@SuppressWarnings("unchecked")
void considersComparatorForPropertyOrder() {

var entity = createEntity(Person.class,
Comparator.comparing(PersistentProperty::getName));
var entity = createEntity(Person.class, Comparator.comparing(PersistentProperty::getName));

var lastName = (T) Mockito.mock(PersistentProperty.class);
when(lastName.getName()).thenReturn("lastName");
Expand Down Expand Up @@ -198,8 +199,8 @@ void returnsGeneratedPropertyAccessorForPropertyAccessor() {
assertThat(accessor).isNotInstanceOf(BeanWrapper.class);
assertThat(accessor).isInstanceOfSatisfying(InstantiationAwarePropertyAccessor.class, it -> {

var delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils
.getField(it, "delegateFunction");
var delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils.getField(it,
"delegateFunction");

var delegate = delegateFunction.apply(value);
assertThat(delegate.getClass().getName()).contains("_Accessor_");
Expand Down Expand Up @@ -359,6 +360,18 @@ void exposesPropertyPopulationNotRequired() {
.forEach(it -> assertThat(createPopulatedPersistentEntity(it).requiresPropertyPopulation()).isFalse());
}

@ParameterizedTest // GH-1432
@ValueSource(classes = { WithTransient.class, RecordWithTransient.class, DataClassWithTransientProperty.class })
void includesTransientProperty(Class<?> classUnderTest) {

PersistentEntity<Object, ?> entity = createPopulatedPersistentEntity(classUnderTest);

assertThat(entity).extracting(PersistentProperty::getName).hasSize(1).containsOnly("firstname");
assertThat(entity.isTransient("firstname")).isFalse();
assertThat(entity.isTransient("lastname")).isTrue();
assertThat(entity.getTransientProperty("lastname").getName()).isEqualTo("lastname");
}

@Test // #2325
void doWithAllInvokesPropertyHandlerForBothAPropertiesAndAssociations() {

Expand Down Expand Up @@ -475,11 +488,23 @@ public PropertyPopulationNotRequiredWithTransient(String firstname, String lastn
}
}

private static class WithTransient {

String firstname;
@Transient String lastname;

}

record RecordWithTransient(String firstname, @Transient String lastname) {

}

// #2325

static class WithAssociation {

String property;
@Reference WithAssociation association;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package org.springframework.data.mapping.model
import org.jmolecules.ddd.types.AggregateRoot
import org.jmolecules.ddd.types.Identifier
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.Transient
import java.time.LocalDateTime

/**
Expand Down Expand Up @@ -55,6 +56,10 @@ data class SingleSettableProperty constructor(val id: Double = Math.random()) {
val version: Int? = null
}

// note: Kotlin ships also a @Transient annotation to indicate JVM's transient keyword.
data class DataClassWithTransientProperty(val firstname: String, @Transient val lastname: String)
data class DataClassWithTransientProperties(@Transient val foo: String = "foo", @Transient val bar: Int)

data class WithCustomCopyMethod(
val id: String?,
val userId: String,
Expand Down

0 comments on commit 26f6ff3

Please sign in to comment.