Skip to content

Commit 6b77868

Browse files
committed
Polishing of annotation model for object creators.
Move to @PersistenceCreator as canonical annotation to explicitly express constructors and methods to be used to create domain object instances from persistence operations. Removed @factorymethod as it's not needed anymore. @PersistenceConstructor is now deprecated. Renamed EntityCreatorMetadata(Support|Discoverer) to InstanceCreatorMetadata(Support|Discoverer) to avoid further manifestation of the notion of an entity in the metamodel as it's not used to only handle entities.
1 parent 0a7bf1c commit 6b77868

18 files changed

+70
-106
lines changed

src/main/asciidoc/object-mapping.adoc

+7-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ This means we need two fundamental steps:
1717
Spring Data automatically tries to detect a persistent entity's constructor to be used to materialize objects of that type.
1818
The resolution algorithm works as follows:
1919

20-
1. If there is a single static factory method annotated with `@FactoryMethod` then it is used.
20+
1. If there is a single static factory method annotated with `@PersistenceCreator` then it is used.
2121
2. If there is a single constructor, it is used.
22-
3. If there are multiple constructors and exactly one is annotated with `@PersistenceConstructor`, it is used.
22+
3. If there are multiple constructors and exactly one is annotated with `@PersistenceCreator`, it is used.
2323
4. If there's a no-argument constructor, it is used.
2424
Other constructors will be ignored.
2525

@@ -205,9 +205,9 @@ Even if the intent is that the calculation should be preferred, it's important t
205205
<4> The `comment` property is mutable is populated by setting its field directly.
206206
<5> The `remarks` properties are mutable and populated by setting the `comment` field directly or by invoking the setter method for
207207
<6> The class exposes a factory method and a constructor for object creation.
208-
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceConstructor`.
208+
The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceCreator`.
209209
Instead, defaulting of properties is handled within the factory method.
210-
If you want Spring Data to use the factory method for object instantiation, annotate it with `@FactoryMethod`.
210+
If you want Spring Data to use the factory method for object instantiation, annotate it with `@PersistenceCreator`.
211211

212212
[[mapping.general-recommendations]]
213213
== General recommendations
@@ -217,7 +217,7 @@ Also, this avoids your domain objects to be littered with setter methods that al
217217
If you need those, prefer to make them package protected so that they can only be invoked by a limited amount of co-located types.
218218
Constructor-only materialization is up to 30% faster than properties population.
219219
* _Provide an all-args constructor_ -- Even if you cannot or don't want to model your entities as immutable values, there's still value in providing a constructor that takes all properties of the entity as arguments, including the mutable ones, as this allows the object mapping to skip the property population for optimal performance.
220-
* _Use factory methods instead of overloaded constructors to avoid ``@PersistenceConstructor``_ -- With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc.
220+
* _Use factory methods instead of overloaded constructors to avoid ``@PersistenceCreator``_ -- With an all-argument constructor needed for optimal performance, we usually want to expose more application use case specific constructors that omit things like auto-generated identifiers etc.
221221
It's an established pattern to rather use static factory methods to expose these variants of the all-args constructor.
222222
* _Make sure you adhere to the constraints that allow the generated instantiator and property accessor classes to be used_ --
223223
* _For identifiers to be generated, still use a final field in combination with an all-arguments persistence constructor (preferred) or a `with…` method_ --
@@ -307,14 +307,14 @@ data class Person(val id: String, val name: String)
307307
----
308308
====
309309

310-
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 `@PersistenceConstructor` to indicate a constructor preference:
310+
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:
311311

312312
====
313313
[source,kotlin]
314314
----
315315
data class Person(var id: String, val name: String) {
316316
317-
@PersistenceConstructor
317+
@PersistenceCreator
318318
constructor(id: String) : this(id, "unknown")
319319
}
320320
----

src/main/java/org/springframework/data/annotation/FactoryMethod.java

-33
This file was deleted.

src/main/java/org/springframework/data/annotation/PersistenceConstructor.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
*
2626
* @author Jon Brisbin
2727
* @author Mark Paluch
28+
* @author Oliver Drotbohm
29+
* @deprecated in favor of {@link EntityCreatorAnnotation} since 3.0, to be removed in 3.1
2830
*/
2931
@Retention(RetentionPolicy.RUNTIME)
3032
@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE })
31-
@EntityCreatorAnnotation
32-
public @interface PersistenceConstructor {
33-
}
33+
@PersistenceCreator
34+
@Deprecated
35+
public @interface PersistenceConstructor {}

src/main/java/org/springframework/data/annotation/EntityCreatorAnnotation.java renamed to src/main/java/org/springframework/data/annotation/PersistenceCreator.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2011-2021 the original author or authors.
2+
* Copyright 2011-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@
2424
* Marker annotation to declare a constructor or factory method annotation as factory/preferred constructor annotation.
2525
*
2626
* @author Mark Paluch
27+
* @author Oliver Drotbohm
2728
* @since 3.0
2829
*/
2930
@Retention(RetentionPolicy.RUNTIME)
30-
@Target({ ElementType.ANNOTATION_TYPE })
31-
public @interface EntityCreatorAnnotation {
32-
}
31+
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
32+
public @interface PersistenceCreator {}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* @author Mark Paluch
2727
* @since 3.0
2828
*/
29-
public final class FactoryMethod<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
29+
public final class FactoryMethod<T, P extends PersistentProperty<P>> extends InstanceCreatorMetadataSupport<T, P> {
3030

3131
/**
3232
* Creates a new {@link FactoryMethod} from the given {@link Constructor} and {@link Parameter}s.
@@ -49,5 +49,4 @@ public FactoryMethod(Method factoryMethod, Parameter<Object, P>... parameters) {
4949
public Method getFactoryMethod() {
5050
return (Method) getExecutable();
5151
}
52-
5352
}

src/main/java/org/springframework/data/mapping/EntityCreatorMetadata.java renamed to src/main/java/org/springframework/data/mapping/InstanceCreatorMetadata.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,13 @@
1818
import java.util.List;
1919

2020
/**
21-
* Metadata describing a mechanism to create an entity instance.
21+
* Metadata describing a mechanism to create instances of persistent types.
2222
*
2323
* @author Mark Paluch
24+
* @author Oliver Drotbohm
2425
* @since 3.0
2526
*/
26-
public interface EntityCreatorMetadata<P extends PersistentProperty<P>> {
27+
public interface InstanceCreatorMetadata<P extends PersistentProperty<P>> {
2728

2829
/**
2930
* Check whether the given {@link PersistentProperty} is being used as creator parameter.

src/main/java/org/springframework/data/mapping/EntityCreatorMetadataSupport.java renamed to src/main/java/org/springframework/data/mapping/InstanceCreatorMetadataSupport.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,23 @@
2828
* persistent data to objects.
2929
*
3030
* @author Mark Paluch
31+
* @author Oliver Drotbohm
3132
* @since 3.0
3233
*/
33-
class EntityCreatorMetadataSupport<T, P extends PersistentProperty<P>> implements EntityCreatorMetadata<P> {
34+
class InstanceCreatorMetadataSupport<T, P extends PersistentProperty<P>> implements InstanceCreatorMetadata<P> {
3435

3536
private final Executable executable;
3637
private final List<Parameter<Object, P>> parameters;
3738
private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>();
3839

3940
/**
40-
* Creates a new {@link EntityCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s.
41+
* Creates a new {@link InstanceCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s.
4142
*
4243
* @param executable must not be {@literal null}.
4344
* @param parameters must not be {@literal null}.
4445
*/
4546
@SafeVarargs
46-
public EntityCreatorMetadataSupport(Executable executable, Parameter<Object, P>... parameters) {
47+
public InstanceCreatorMetadataSupport(Executable executable, Parameter<Object, P>... parameters) {
4748

4849
Assert.notNull(executable, "Executable must not be null!");
4950
Assert.notNull(parameters, "Parameters must not be null!");
@@ -72,7 +73,7 @@ public List<Parameter<Object, P>> getParameters() {
7273

7374
/**
7475
* Returns whether the given {@link PersistentProperty} is referenced in a creator argument of the
75-
* {@link PersistentEntity} backing this {@link EntityCreatorMetadataSupport}.
76+
* {@link PersistentEntity} backing this {@link InstanceCreatorMetadataSupport}.
7677
* <p>
7778
* Results of this call are cached and reused on the next invocation. Calling this method for a
7879
* {@link PersistentProperty} that was not yet added to its owning {@link PersistentEntity} will capture that state

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
5656
PreferredConstructor<T, P> getPersistenceConstructor();
5757

5858
/**
59-
* Returns the {@link EntityCreatorMetadata} to be used to instantiate objects of this {@link PersistentEntity}.
59+
* Returns the {@link InstanceCreatorMetadata} to be used to instantiate objects of this {@link PersistentEntity}.
6060
*
6161
* @return {@literal null} in case no suitable creation mechanism for automatic construction can be found. This
6262
* usually indicates that the instantiation of the object of that persistent entity is done through either a
@@ -65,7 +65,7 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> extends It
6565
* @since 3.0
6666
*/
6767
@Nullable
68-
EntityCreatorMetadata<P> getEntityCreator();
68+
InstanceCreatorMetadata<P> getEntityCreator();
6969

7070
/**
7171
* Returns whether the given {@link PersistentProperty} is referred to by a constructor argument of the

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* @author Myeonghyeon Lee
3636
* @author Xeno Amess
3737
*/
38-
public final class PreferredConstructor<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
38+
public final class PreferredConstructor<T, P extends PersistentProperty<P>> extends InstanceCreatorMetadataSupport<T, P> {
3939

4040
private final List<Parameter<Object, P>> parameters;
4141

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
6363

6464
private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!";
6565

66-
private final @Nullable EntityCreatorMetadata<P> creator;
66+
private final @Nullable InstanceCreatorMetadata<P> creator;
6767
private final TypeInformation<T> information;
6868
private final List<P> properties;
6969
private final List<P> persistentPropertiesCache;
@@ -109,7 +109,7 @@ public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparato
109109
this.properties = new ArrayList<>();
110110
this.persistentPropertiesCache = new ArrayList<>();
111111
this.comparator = comparator;
112-
this.creator = EntityCreatorMetadataDiscoverer.discover(this);
112+
this.creator = InstanceCreatorMetadataDiscoverer.discover(this);
113113
this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator));
114114

115115
this.propertyCache = new HashMap<>(16, 1f);
@@ -134,7 +134,7 @@ public PreferredConstructor<T, P> getPersistenceConstructor() {
134134
}
135135

136136
@Override
137-
public EntityCreatorMetadata<P> getEntityCreator() {
137+
public InstanceCreatorMetadata<P> getEntityCreator() {
138138
return creator;
139139
}
140140

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.springframework.beans.BeanInstantiationException;
3434
import org.springframework.cglib.core.ReflectUtils;
3535
import org.springframework.core.NativeDetector;
36-
import org.springframework.data.mapping.EntityCreatorMetadata;
36+
import org.springframework.data.mapping.InstanceCreatorMetadata;
3737
import org.springframework.data.mapping.FactoryMethod;
3838
import org.springframework.data.mapping.Parameter;
3939
import org.springframework.data.mapping.PersistentEntity;
@@ -227,15 +227,15 @@ static Object[] allocateArguments(int argumentCount) {
227227

228228
/**
229229
* Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and
230-
* {@link EntityCreatorMetadata}. There will always be exactly one {@link ObjectInstantiator} instance per
230+
* {@link InstanceCreatorMetadata}. There will always be exactly one {@link ObjectInstantiator} instance per
231231
* {@link PersistentEntity}.
232232
*
233233
* @param entity
234234
* @param constructor
235235
* @return
236236
*/
237237
ObjectInstantiator createObjectInstantiator(PersistentEntity<?, ?> entity,
238-
@Nullable EntityCreatorMetadata<?> constructor) {
238+
@Nullable InstanceCreatorMetadata<?> constructor) {
239239

240240
try {
241241
return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance();
@@ -287,7 +287,7 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
287287
* @return
288288
*/
289289
static <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
290-
@Nullable EntityCreatorMetadata<P> constructor, ParameterValueProvider<P> provider) {
290+
@Nullable InstanceCreatorMetadata<P> constructor, ParameterValueProvider<P> provider) {
291291

292292
if (constructor == null || !constructor.hasParameters()) {
293293
return allocateArguments(0);
@@ -400,7 +400,7 @@ static class ObjectInstantiatorClassGenerator {
400400
* @return
401401
*/
402402
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
403-
@Nullable EntityCreatorMetadata<?> constructor) {
403+
@Nullable InstanceCreatorMetadata<?> constructor) {
404404

405405
var className = generateClassName(entity);
406406
var type = entity.getType();
@@ -441,7 +441,7 @@ private String generateClassName(PersistentEntity<?, ?> entity) {
441441
* @return
442442
*/
443443
public byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity,
444-
@Nullable EntityCreatorMetadata<?> entityCreator) {
444+
@Nullable InstanceCreatorMetadata<?> entityCreator) {
445445

446446
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
447447

@@ -476,7 +476,7 @@ private void visitDefaultConstructor(ClassWriter cw) {
476476
* @param entityCreator
477477
*/
478478
private void visitCreateMethod(ClassWriter cw, PersistentEntity<?, ?> entity,
479-
@Nullable EntityCreatorMetadata<?> entityCreator) {
479+
@Nullable InstanceCreatorMetadata<?> entityCreator) {
480480

481481
var entityTypeResourcePath = Type.getInternalName(entity.getType());
482482

src/main/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscoverer.java renamed to src/main/java/org/springframework/data/mapping/model/InstanceCreatorMetadataDiscoverer.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import org.springframework.core.DefaultParameterNameDiscoverer;
2626
import org.springframework.core.ParameterNameDiscoverer;
2727
import org.springframework.core.annotation.MergedAnnotations;
28-
import org.springframework.data.annotation.EntityCreatorAnnotation;
29-
import org.springframework.data.mapping.EntityCreatorMetadata;
28+
import org.springframework.data.annotation.PersistenceCreator;
29+
import org.springframework.data.mapping.InstanceCreatorMetadata;
3030
import org.springframework.data.mapping.FactoryMethod;
3131
import org.springframework.data.mapping.MappingException;
3232
import org.springframework.data.mapping.Parameter;
@@ -40,7 +40,7 @@
4040
* @author Mark Paluch
4141
* @since 3.0
4242
*/
43-
class EntityCreatorMetadataDiscoverer {
43+
class InstanceCreatorMetadataDiscoverer {
4444

4545
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
4646

@@ -53,13 +53,13 @@ class EntityCreatorMetadataDiscoverer {
5353
* @return
5454
*/
5555
@Nullable
56-
public static <T, P extends PersistentProperty<P>> EntityCreatorMetadata<P> discover(PersistentEntity<T, P> entity) {
56+
public static <T, P extends PersistentProperty<P>> InstanceCreatorMetadata<P> discover(PersistentEntity<T, P> entity) {
5757

5858
var declaredConstructors = entity.getType().getDeclaredConstructors();
5959
var declaredMethods = entity.getType().getDeclaredMethods();
6060

61-
var hasAnnotatedFactoryMethod = findAnnotation(EntityCreatorAnnotation.class, declaredMethods);
62-
var hasAnnotatedConstructor = findAnnotation(EntityCreatorAnnotation.class, declaredConstructors);
61+
var hasAnnotatedFactoryMethod = findAnnotation(PersistenceCreator.class, declaredMethods);
62+
var hasAnnotatedConstructor = findAnnotation(PersistenceCreator.class, declaredConstructors);
6363

6464
if (hasAnnotatedConstructor && hasAnnotatedFactoryMethod) {
6565
throw new MappingException(
@@ -92,7 +92,7 @@ private static <T, P extends PersistentProperty<P>> List<Method> discoverFactory
9292
continue;
9393
}
9494

95-
if (findAnnotation(EntityCreatorAnnotation.class, method)) {
95+
if (findAnnotation(PersistenceCreator.class, method)) {
9696
candidates.add(method);
9797
}
9898
}
@@ -124,11 +124,11 @@ private static <T, P extends PersistentProperty<P>> FactoryMethod<Object, P> get
124124

125125
private static void validateMethod(Method method) {
126126

127-
if (MergedAnnotations.from(method).isPresent(EntityCreatorAnnotation.class)) {
127+
if (MergedAnnotations.from(method).isPresent(PersistenceCreator.class)) {
128128

129129
if (!Modifier.isStatic(method.getModifiers())) {
130130
throw new MappingException(
131-
"@Factory can only be used on static methods. Offending method: %s".formatted(method));
131+
"@PersistenceCreator can only be used on static methods. Offending method: %s".formatted(method));
132132
}
133133
}
134134
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import java.util.List;
2525
import java.util.stream.IntStream;
2626

27-
import org.springframework.data.mapping.EntityCreatorMetadata;
27+
import org.springframework.data.mapping.InstanceCreatorMetadata;
2828
import org.springframework.data.mapping.Parameter;
2929
import org.springframework.data.mapping.PersistentEntity;
3030
import org.springframework.data.mapping.PersistentProperty;
@@ -201,7 +201,7 @@ public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentPrope
201201
}
202202

203203
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
204-
@Nullable EntityCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
204+
@Nullable InstanceCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
205205

206206
if (entityCreator == null) {
207207
throw new IllegalArgumentException("EntityCreator must not be null!");

0 commit comments

Comments
 (0)