Skip to content

Commit d43913f

Browse files
committed
Include transient properties in persistent entity metamodel.
1 parent 0d9eeaf commit d43913f

File tree

6 files changed

+115
-32
lines changed

6 files changed

+115
-32
lines changed

src/main/java/org/springframework/data/mapping/PersistentEntity.java

+41-19
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
4747
* Returns the {@link PreferredConstructor} to be used to instantiate objects of this {@link PersistentEntity}.
4848
*
4949
* @return {@literal null} in case no suitable constructor for automatic construction can be found. This usually
50-
* indicates that the instantiation of the object of that persistent entity is done through either a
51-
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom
52-
* conversion mechanisms entirely.
50+
* indicates that the instantiation of the object of that persistent entity is done through either a customer
51+
* {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion
52+
* mechanisms entirely.
5353
* @deprecated since 3.0, use {@link #getInstanceCreatorMetadata()}.
5454
*/
5555
@Nullable
@@ -61,8 +61,8 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
6161
*
6262
* @return {@literal null} in case no suitable creation mechanism for automatic construction can be found. This
6363
* usually indicates that the instantiation of the object of that persistent entity is done through either a
64-
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom
65-
* conversion mechanisms entirely.
64+
* customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion
65+
* mechanisms entirely.
6666
* @since 3.0
6767
*/
6868
@Nullable
@@ -136,17 +136,17 @@ default P getRequiredIdProperty() {
136136
}
137137

138138
/**
139-
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property
140-
* is available on the entity.
139+
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property is
140+
* available on the entity.
141141
*
142142
* @return the version property of the {@link PersistentEntity}.
143143
*/
144144
@Nullable
145145
P getVersionProperty();
146146

147147
/**
148-
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property
149-
* is available on the entity.
148+
* Returns the version property of the {@link PersistentEntity}. Can be {@literal null} in case no version property is
149+
* available on the entity.
150150
*
151151
* @return the version property of the {@link PersistentEntity}.
152152
* @throws IllegalStateException if {@link PersistentEntity} does not define a {@literal version} property.
@@ -166,7 +166,7 @@ default P getRequiredVersionProperty() {
166166
/**
167167
* Obtains a {@link PersistentProperty} instance by name.
168168
*
169-
* @param name The name of the property. Can be {@literal null}.
169+
* @param name the name of the property. Can be {@literal null}.
170170
* @return the {@link PersistentProperty} or {@literal null} if it doesn't exist.
171171
*/
172172
@Nullable
@@ -213,6 +213,28 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
213213
*/
214214
Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType);
215215

216+
/**
217+
* Obtains a transient {@link PersistentProperty} instance by name. You can check with {@link #isTransient(String)}
218+
* whether there is a transient property before calling this method.
219+
*
220+
* @param name the name of the property. Can be {@literal null}.
221+
* @return the {@link PersistentProperty} or {@literal null} if it doesn't exist.
222+
* @since 3.3
223+
* @see #isTransient(String)
224+
*/
225+
@Nullable
226+
P getTransientProperty(String name);
227+
228+
/**
229+
* Returns whether the property is transient.
230+
*
231+
* @param property name of the property.
232+
* @return {@code true} if the property is transient. Applies only for existing properties. {@code false} if the
233+
* property does not exist or is not transient.
234+
* @since 3.3
235+
*/
236+
boolean isTransient(String property);
237+
216238
/**
217239
* Returns whether the {@link PersistentEntity} has an id property. If this call returns {@literal true},
218240
* {@link #getIdProperty()} will return a non-{@literal null} value.
@@ -237,8 +259,8 @@ default P getPersistentProperty(Class<? extends Annotation> annotationType) {
237259
Class<T> getType();
238260

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

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

286308
/**
287-
* Applies the given {@link PropertyHandler} to both all {@link PersistentProperty}s as well as all inverse
288-
* properties of all {@link Association}s. The iteration order is undefined.
309+
* Applies the given {@link PropertyHandler} to both all {@link PersistentProperty}s as well as all inverse properties
310+
* of all {@link Association}s. The iteration order is undefined.
289311
*
290312
* @param handler must not be {@literal null}.
291313
* @since 2.5
@@ -370,7 +392,7 @@ default <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType)
370392
*
371393
* @param bean must not be {@literal null}.
372394
* @throws IllegalArgumentException in case the given bean is not an instance of the typ represented by the
373-
* {@link PersistentEntity}.
395+
* {@link PersistentEntity}.
374396
* @return whether the given bean is considered a new instance.
375397
*/
376398
boolean isNew(Object bean);
@@ -386,8 +408,8 @@ default <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType)
386408
boolean isImmutable();
387409

388410
/**
389-
* Returns whether the entity needs properties to be populated, i.e. if any property exists that's not initialized
390-
* by the constructor.
411+
* Returns whether the entity needs properties to be populated, i.e. if any property exists that's not initialized by
412+
* the constructor.
391413
*
392414
* @return
393415
* @since 2.1

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

-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333

3434
import org.apache.commons.logging.Log;
3535
import org.apache.commons.logging.LogFactory;
36-
3736
import org.springframework.beans.BeanUtils;
3837
import org.springframework.beans.BeansException;
3938
import org.springframework.beans.factory.InitializingBean;
@@ -563,10 +562,6 @@ private void createAndRegisterProperty(Property input) {
563562

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

566-
if (property.isTransient()) {
567-
return;
568-
}
569-
570565
if (!input.isFieldBacked() && !property.usePropertyAccess()) {
571566
return;
572567
}

src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java

+32-4
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,14 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
6767
private final @Nullable InstanceCreatorMetadata<P> creator;
6868
private final TypeInformation<T> information;
6969
private final List<P> properties;
70+
private final List<P> transientProperties;
7071
private final List<P> persistentPropertiesCache;
7172
private final @Nullable Comparator<P> comparator;
7273
private final Set<Association<P>> associations;
7374

7475
private final Map<String, P> propertyCache;
76+
77+
private final Map<String, P> transientPropertyCache;
7578
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
7679
private final MultiValueMap<Class<? extends Annotation>, P> propertyAnnotationCache;
7780

@@ -108,12 +111,14 @@ public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparato
108111

109112
this.information = information;
110113
this.properties = new ArrayList<>();
114+
this.transientProperties = new ArrayList<>(0);
111115
this.persistentPropertiesCache = new ArrayList<>();
112116
this.comparator = comparator;
113117
this.creator = InstanceCreatorMetadataDiscoverer.discover(this);
114118
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
115119

116120
this.propertyCache = new HashMap<>(16, 1f);
121+
this.transientPropertyCache = new HashMap<>(0, 1f);
117122
this.annotationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK);
118123
this.propertyAnnotationCache = CollectionUtils
119124
.toMultiValueMap(new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK));
@@ -180,6 +185,18 @@ public void addPersistentProperty(P property) {
180185

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

188+
if (property.isTransient()) {
189+
190+
if (transientProperties.contains(property)) {
191+
return;
192+
}
193+
194+
transientProperties.add(property);
195+
transientPropertyCache.put(property.getName(), property);
196+
197+
return;
198+
}
199+
183200
if (properties.contains(property)) {
184201
return;
185202
}
@@ -205,10 +222,8 @@ public void addPersistentProperty(P property) {
205222
if (versionProperty != null) {
206223

207224
throw new MappingException(
208-
String.format(
209-
"Attempt to add version property %s but already have property %s registered "
210-
+ "as version; Check your mapping configuration",
211-
property.getField(), versionProperty.getField()));
225+
String.format("Attempt to add version property %s but already have property %s registered "
226+
+ "as version; Check your mapping configuration", property.getField(), versionProperty.getField()));
212227
}
213228

214229
this.versionProperty = property;
@@ -256,6 +271,19 @@ public P getPersistentProperty(String name) {
256271
return propertyCache.get(name);
257272
}
258273

274+
@Override
275+
public P getTransientProperty(String name) {
276+
return transientPropertyCache.get(name);
277+
}
278+
279+
@Override
280+
public boolean isTransient(String property) {
281+
282+
P transientProperty = getTransientProperty(property);
283+
284+
return transientProperty != null && transientProperty.isTransient();
285+
}
286+
259287
@Override
260288
public Iterable<P> getPersistentProperties(Class<? extends Annotation> annotationType) {
261289

src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java

+8
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.mapping.model;
1717

18+
import org.springframework.data.annotation.Transient;
1819
import org.springframework.data.mapping.InstanceCreatorMetadata;
1920
import org.springframework.data.mapping.MappingException;
2021
import org.springframework.data.mapping.Parameter;
@@ -55,6 +56,13 @@ public <T> T getParameterValue(Parameter<T, P> parameter) {
5556
return (T) parent;
5657
}
5758

59+
if (parameter.getAnnotations().isPresent(Transient.class)) {
60+
61+
// parameter.getRawType().isPrimitive()
62+
return null;
63+
64+
}
65+
5866
String name = parameter.getName();
5967

6068
if (name == null) {

src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java

+29-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
import org.junit.jupiter.api.Test;
3333
import org.junit.jupiter.api.extension.ExtendWith;
34+
import org.junit.jupiter.params.ParameterizedTest;
35+
import org.junit.jupiter.params.provider.ValueSource;
3436
import org.mockito.Mock;
3537
import org.mockito.Mockito;
3638
import org.mockito.junit.jupiter.MockitoExtension;
@@ -105,8 +107,7 @@ void returnsTypeAliasIfAnnotated() {
105107
@SuppressWarnings("unchecked")
106108
void considersComparatorForPropertyOrder() {
107109

108-
var entity = createEntity(Person.class,
109-
Comparator.comparing(PersistentProperty::getName));
110+
var entity = createEntity(Person.class, Comparator.comparing(PersistentProperty::getName));
110111

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

201-
var delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils
202-
.getField(it, "delegateFunction");
202+
var delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils.getField(it,
203+
"delegateFunction");
203204

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

363+
@ParameterizedTest // GH-1432
364+
@ValueSource(classes = { WithTransient.class, RecordWithTransient.class, DataClassWithTransientProperty.class })
365+
void includesTransientProperty(Class<?> classUnderTest) {
366+
367+
PersistentEntity<Object, ?> entity = createPopulatedPersistentEntity(classUnderTest);
368+
369+
assertThat(entity).extracting(PersistentProperty::getName).hasSize(1).containsOnly("firstname");
370+
assertThat(entity.isTransient("firstname")).isFalse();
371+
assertThat(entity.isTransient("lastname")).isTrue();
372+
assertThat(entity.getTransientProperty("lastname").getName()).isEqualTo("lastname");
373+
}
374+
362375
@Test // #2325
363376
void doWithAllInvokesPropertyHandlerForBothAPropertiesAndAssociations() {
364377

@@ -475,11 +488,23 @@ public PropertyPopulationNotRequiredWithTransient(String firstname, String lastn
475488
}
476489
}
477490

491+
private static class WithTransient {
492+
493+
String firstname;
494+
@Transient String lastname;
495+
496+
}
497+
498+
record RecordWithTransient(String firstname, @Transient String lastname) {
499+
500+
}
501+
478502
// #2325
479503

480504
static class WithAssociation {
481505

482506
String property;
483507
@Reference WithAssociation association;
484508
}
509+
485510
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mapping.model
1717

1818
import org.springframework.data.annotation.Id
19+
import org.springframework.data.annotation.Transient
1920
import java.time.LocalDateTime
2021

2122
/**
@@ -33,6 +34,10 @@ data class SingleSettableProperty constructor(val id: Double = Math.random()) {
3334
val version: Int? = null
3435
}
3536

37+
// note: Kotlin ships also a @Transient annotation to indicate JVM's transient keyword.
38+
data class DataClassWithTransientProperty(val firstname: String, @Transient val lastname: String)
39+
data class DataClassWithTransientProperties(@Transient val foo: String = "foo", @Transient val bar: Int)
40+
3641
data class WithCustomCopyMethod(
3742
val id: String?,
3843
val userId: String,

0 commit comments

Comments
 (0)