Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @Transient properties to PersistentEntity and use value defaulting for transient constructor properties #2985

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.4.0-SNAPSHOT</version>
<version>3.4.0-ISSUE-1432-SNAPSHOT</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
Expand Down
12 changes: 8 additions & 4 deletions src/main/antora/modules/ROOT/pages/object-mapping.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ By default, Spring Data attempts to use generated property accessors and falls b
Let's have a look at the following entity:

.A sample entity
[source, java]
[source,java]
----
class Person {

Expand All @@ -165,14 +165,15 @@ class Person {

private String comment; <4>
private @AccessType(Type.PROPERTY) String remarks; <5>
private @Transient String summary; <6>

static Person of(String firstname, String lastname, LocalDate birthday) { <6>
static Person of(String firstname, String lastname, LocalDate birthday) { <7>

return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}

Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { <6>
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { <7>

this.id = id;
this.firstname = firstname;
Expand Down Expand Up @@ -201,7 +202,10 @@ With the design shown, the database value will trump the defaulting as Spring Da
Even if the intent is that the calculation should be preferred, it's important that this constructor also takes `age` as parameter (to potentially ignore it) as otherwise the property population step will attempt to set the age field and fail due to it being immutable and no `with…` method being present.
<4> The `comment` property is mutable and is populated by setting its field directly.
<5> The `remarks` property is mutable and is populated by invoking the setter method.
<6> The class exposes a factory method and a constructor for object creation.
<6> The `summary` property transient and will not be persisted as it is annotated with `@Transient`.
Spring Data doesn't use Java's `transient` keyword to exclude properties from being persisted as `transient` is part of the Java Serialization Framework.
Note that this property can be used with a persistence constructor, but its value will default to `null` (or the respective primitive initial value if the property type was a primitive one).
<7> The class exposes a factory method and a constructor for object creation.
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceCreator`.
Instead, defaulting of properties is handled within the factory method.
If you want Spring Data to use the factory method for object instantiation, annotate it with `@PersistenceCreator`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@

/**
* Marker annotation to declare a constructor or factory method annotation as factory/preferred constructor annotation.
* Properties used by the constructor (or factory method) must refer to persistent properties or be annotated with
* {@link org.springframework.beans.factory.annotation.Value @Value(…)} to obtain a value for object creation.
*
* @author Mark Paluch
* @author Oliver Drotbohm
* @since 3.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface PersistenceCreator {}
public @interface PersistenceCreator {
}
13 changes: 10 additions & 3 deletions src/main/java/org/springframework/data/annotation/Transient.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@
import java.lang.annotation.Target;

/**
* Marks a field to be transient for the mapping framework. Thus the property will not be persisted and not further
* inspected by the mapping framework.
* Marks a field to be transient for the mapping framework. Thus, the property will not be persisted.
* <p>
* Excluding properties from the persistence mechanism is separate from Java's {@code transient} keyword that serves the
* purpose of excluding properties from being serialized through Java Serialization.
* <p>
* Transient properties can be used in {@link PersistenceCreator constructor creation/factory methods}, however they
* will use Java default values. We highly recommend using {@link org.springframework.beans.factory.annotation.Value
* SpEL expressions through @Value(…)} to provide a meaningful value.
*
* @author Oliver Gierke
* @author Jon Brisbin
* @author Mark Paluch
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE, RECORD_COMPONENT })
public @interface Transient {
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public AuditingHandlerSupport(PersistentEntities entities) {
/**
* Setter do determine if {@link Auditable#setCreatedDate(TemporalAccessor)}} and
* {@link Auditable#setLastModifiedDate(TemporalAccessor)} shall be filled with the current Java time. Defaults to
* {@code true}. One might set this to {@code false} to use database features to set entity time.
* {@literal true}. One might set this to {@literal false} to use database features to set entity time.
*
* @param dateTimeForNow the dateTimeForNow to set
*/
Expand All @@ -71,7 +71,7 @@ public void setDateTimeForNow(boolean dateTimeForNow) {

/**
* Set this to true if you want to treat entity creation as modification and thus setting the current date as
* modification date during creation, too. Defaults to {@code true}.
* modification date during creation, too. Defaults to {@literal true}.
*
* @param modifyOnCreation if modification information shall be set on creation, too
*/
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/springframework/data/domain/Window.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ static <T> Window<T> from(List<T> items, IntFunction<? extends ScrollPosition> p
int size();

/**
* Returns {@code true} if this window contains no elements.
* Returns {@literal true} if this window contains no elements.
*
* @return {@code true} if this window contains no elements
* @return {@literal true} if this window contains no elements
*/
@Override
boolean isEmpty();
Expand Down Expand Up @@ -102,7 +102,7 @@ default boolean isLast() {
* Returns whether the underlying scroll mechanism can provide a {@link ScrollPosition} at {@code index}.
*
* @param index
* @return {@code true} if a {@link ScrollPosition} can be created; {@code false} otherwise.
* @return {@literal true} if a {@link ScrollPosition} can be created; {@literal false} otherwise.
* @see #positionAt(int)
*/
default boolean hasPosition(int index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ default ExpressionDependencies getExpressionDependencies() {
/**
* Returns whether the expression is a literal expression (that doesn't actually require evaluation).
*
* @return {@code true} if the expression is a literal expression; {@code false} if the expression can yield a
* @return {@literal true} if the expression is a literal expression; {@literal false} if the expression can yield a
* different result upon {@link #evaluate(ValueEvaluationContext) evaluation}.
*/
boolean isLiteral();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/springframework/data/mapping/Alias.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

/**
* A container object which may or may not contain a type alias value. If a value is present, {@code isPresent()} will
* return {@code true} and {@link #getValue()} will return the value.
* return {@literal true} and {@link #getValue()} will return the value.
* <p>
* Additional methods that depend on the presence or absence of a contained value are provided, such as
* {@link #hasValue(Object)} or {@link #isPresent()}
Expand Down
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 {@literal true} if the property is transient. Applies only for existing properties. {@literal 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 @@ -264,10 +264,10 @@ public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotatio
}

/**
* Returns whether the property carries the an annotation of the given type.
* Returns whether the property carries the annotation of the given type.
*
* @param annotationType the annotation type to look up.
* @return
* @return {@literal true} if the annotation is present, {@literal false} otherwise.
*/
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
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 @@ -348,7 +348,7 @@ public Class<?> getParameterType() {
}

/**
* @return {@code true} if the value hierarchy applies boxing.
* @return {@literal true} if the value hierarchy applies boxing.
*/
public boolean appliesBoxing() {
return applyBoxing;
Expand Down
Loading