information;
private final List properties;
+ private final List
transientProperties;
private final List
persistentPropertiesCache;
private final @Nullable Comparator
comparator;
private final Set> associations;
private final Map propertyCache;
+
+ private final Map transientPropertyCache;
private final Map, Optional> annotationCache;
private final MultiValueMap, P> propertyAnnotationCache;
@@ -114,12 +117,14 @@ public BasicPersistentEntity(TypeInformation 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));
this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE;
@@ -186,6 +191,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;
}
@@ -279,6 +296,19 @@ public P getPersistentProperty(String name) {
return propertyCache.get(name);
}
+ @Override
+ public @Nullable 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 getPersistentProperties(Class extends Annotation> annotationType) {
diff --git a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java
index 72be8bb742..bf7d71fc01 100644
--- a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java
+++ b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java
@@ -35,6 +35,7 @@
* @author Oliver Drotbohm
* @author Mark Paluch
* @author Johannes Englmeier
+ * @author Christoph Strobl
* @since 2.3
*/
public class InstantiationAwarePropertyAccessor implements PersistentPropertyAccessor {
@@ -110,9 +111,16 @@ public void setProperty(PersistentProperty> property, @Nullable Object value)
@SuppressWarnings("NullAway")
public @Nullable Object getParameterValue(Parameter parameter) {
- return property.getName().equals(parameter.getName()) //
- ? value
- : delegate.getProperty(owner.getRequiredPersistentProperty(parameter.getName()));
+ if (property.getName().equals(parameter.getName())) {
+ return value;
+ }
+
+ String paramName = parameter.getName();
+ if (paramName != null && parameter.isTransient()) {
+ return ParameterValueProvider.getDefaultValue(parameter.getRawType());
+ }
+
+ return delegate.getProperty(owner.getRequiredPersistentProperty(paramName));
}
});
}
diff --git a/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java b/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java
index f4aa3813ed..d55cb36975 100644
--- a/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java
+++ b/src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java
@@ -349,7 +349,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;
diff --git a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java
index 1a490669bf..60b0d45578 100644
--- a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java
+++ b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java
@@ -16,14 +16,15 @@
package org.springframework.data.mapping.model;
import org.jspecify.annotations.Nullable;
-
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.data.util.ReflectionUtils;
/**
* Callback interface to lookup values for a given {@link Parameter}.
*
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
public interface ParameterValueProvider> {
@@ -34,4 +35,13 @@ public interface ParameterValueProvider
> {
* @return the property value. Can be {@literal null}.
*/
@Nullable T getParameterValue(Parameter parameter);
+
+ /**
+ * @param parameterType raw parameter type
+ * @return {@literal null} or primitive default for given parameter type.
+ * @since 4.1
+ */
+ static @Nullable Object getDefaultValue(Class> parameterType) {
+ return parameterType.isPrimitive() ? ReflectionUtils.getPrimitiveDefault(parameterType) : null;
+ }
}
diff --git a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java
index c0a3a824af..0c875e99f6 100644
--- a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java
+++ b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java
@@ -29,6 +29,7 @@
*
* @author Oliver Gierke
* @author Johannes Englmeier
+ * @author Mark Paluch
*/
public class PersistentEntityParameterValueProvider>
implements ParameterValueProvider
{
@@ -50,12 +51,15 @@ public PersistentEntityParameterValueProvider(PersistentEntity, P> entity, Pro
public T getParameterValue(Parameter parameter) {
InstanceCreatorMetadata creator = entity.getInstanceCreatorMetadata();
+ String name = parameter.getName();
if (creator != null && creator.isParentParameter(parameter)) {
return (T) parent;
}
- String name = parameter.getName();
+ if (parameter.isTransient()) {
+ return (T) ParameterValueProvider.getDefaultValue(parameter.getRawType());
+ }
if (name == null) {
throw new MappingException(String.format("Parameter %s does not have a name", parameter));
diff --git a/src/main/java/org/springframework/data/projection/EntityProjection.java b/src/main/java/org/springframework/data/projection/EntityProjection.java
index 4e69673778..0209a5c157 100644
--- a/src/main/java/org/springframework/data/projection/EntityProjection.java
+++ b/src/main/java/org/springframework/data/projection/EntityProjection.java
@@ -159,14 +159,14 @@ public TypeInformation> getActualDomainType() {
}
/**
- * @return {@code true} if the {@link #getMappedType()} is a projection.
+ * @return {@literal true} if the {@link #getMappedType()} is a projection.
*/
public boolean isProjection() {
return projection;
}
/**
- * @return {@code true} if the {@link #getMappedType()} is a closed projection.
+ * @return {@literal true} if the {@link #getMappedType()} is a closed projection.
*/
public boolean isClosedProjection() {
return isProjection()
diff --git a/src/main/java/org/springframework/data/projection/EntityProjectionIntrospector.java b/src/main/java/org/springframework/data/projection/EntityProjectionIntrospector.java
index ad8538c21f..76a0c712ff 100644
--- a/src/main/java/org/springframework/data/projection/EntityProjectionIntrospector.java
+++ b/src/main/java/org/springframework/data/projection/EntityProjectionIntrospector.java
@@ -223,13 +223,13 @@ public interface ProjectionPredicate {
*
* @param target the target type.
* @param underlyingType the underlying type.
- * @return {@code true} if the input argument matches the predicate, otherwise {@code false}.
+ * @return {@literal true} if the input argument matches the predicate, otherwise {@literal false}.
*/
boolean test(Class> target, Class> underlyingType);
/**
* Return a composed predicate that represents a short-circuiting logical AND of this predicate and another. When
- * evaluating the composed predicate, if this predicate is {@code false}, then the {@code other} predicate is not
+ * evaluating the composed predicate, if this predicate is {@literal false}, then the {@code other} predicate is not
* evaluated.
*
* Any exceptions thrown during evaluation of either predicate are relayed to the caller; if evaluation of this
diff --git a/src/main/java/org/springframework/data/repository/config/PropertiesBasedNamedQueriesFactoryBean.java b/src/main/java/org/springframework/data/repository/config/PropertiesBasedNamedQueriesFactoryBean.java
index efee797d97..daa0ee7817 100644
--- a/src/main/java/org/springframework/data/repository/config/PropertiesBasedNamedQueriesFactoryBean.java
+++ b/src/main/java/org/springframework/data/repository/config/PropertiesBasedNamedQueriesFactoryBean.java
@@ -48,7 +48,7 @@ public class PropertiesBasedNamedQueriesFactoryBean extends PropertiesLoaderSupp
* Set whether a shared singleton {@code PropertiesBasedNamedQueries} instance should be created, or rather a new
* {@code PropertiesBasedNamedQueries} instance on each request.
*
- * Default is {@code true} (a shared singleton).
+ * Default is {@literal true} (a shared singleton).
*/
public void setSingleton(boolean singleton) {
this.singleton = singleton;
diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java
index c5d84f8986..49af8690c5 100644
--- a/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java
+++ b/src/main/java/org/springframework/data/repository/core/RepositoryMethodContextHolder.java
@@ -35,7 +35,7 @@ public class RepositoryMethodContextHolder {
/**
* ThreadLocal holder for repository method associated with this thread. Will contain {@code null} unless the
- * "exposeMetadata" property on the controlling repository factory configuration has been set to {@code true}.
+ * "exposeMetadata" property on the controlling repository factory configuration has been set to {@literal true}.
*/
private static final ThreadLocal currentMethod = new NamedThreadLocal<>(
"Current Repository Method");
diff --git a/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java b/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java
index d475a65fec..5f21a30724 100644
--- a/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java
+++ b/src/main/java/org/springframework/data/repository/core/support/MethodLookup.java
@@ -52,8 +52,8 @@ public interface MethodLookup {
/**
* Returns a composed {@link MethodLookup} that represents a concatenation of this predicate and another. When
- * evaluating the composed method lookup, if this lookup evaluates {@code true}, then the {@code other} method lookup
- * is not evaluated.
+ * evaluating the composed method lookup, if this lookup evaluates {@literal true}, then the {@code other} method
+ * lookup is not evaluated.
*
* @param other must not be {@literal null}.
* @return the composed {@link MethodLookup}.
diff --git a/src/main/java/org/springframework/data/spel/spi/Function.java b/src/main/java/org/springframework/data/spel/spi/Function.java
index 42f425b55b..e92f2a8cb7 100644
--- a/src/main/java/org/springframework/data/spel/spi/Function.java
+++ b/src/main/java/org/springframework/data/spel/spi/Function.java
@@ -154,7 +154,7 @@ public int getParameterCount() {
* Checks if the encapsulated method has exactly the argument types as those passed as an argument.
*
* @param argumentTypes a list of {@link TypeDescriptor}s to compare with the argument types of the method
- * @return {@code true} if the types are equal, {@code false} otherwise.
+ * @return {@literal true} if the types are equal, {@literal false} otherwise.
*/
public boolean supportsExact(List argumentTypes) {
return ParameterTypes.of(argumentTypes).exactlyMatchParametersOf(method);
@@ -164,7 +164,7 @@ public boolean supportsExact(List argumentTypes) {
* Checks whether this {@code Function} has the same signature as another {@code Function}.
*
* @param other the {@code Function} to compare {@code this} with.
- * @return {@code true} if name and argument list are the same.
+ * @return {@literal true} if name and argument list are the same.
*/
public boolean isSignatureEqual(Function other) {
diff --git a/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java b/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java
index f865414db1..ef0941d285 100644
--- a/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java
+++ b/src/main/java/org/springframework/data/util/KotlinReflectionUtils.java
@@ -136,7 +136,7 @@ public static Class> getReturnType(Method method) {
* Returns whether the given {@link KType} is a {@link KClass#isValue() value} class.
*
* @param type the kotlin type to inspect.
- * @return {@code true} the type is a value class.
+ * @return {@literal true} the type is a value class.
* @since 3.2
*/
public static boolean isValueClass(KType type) {
@@ -148,7 +148,7 @@ public static boolean isValueClass(KType type) {
* Returns whether the given class makes uses Kotlin {@link KClass#isValue() value} classes.
*
* @param type the kotlin type to inspect.
- * @return {@code true} when at least one property uses Kotlin value classes.
+ * @return {@literal true} when at least one property uses Kotlin value classes.
* @since 3.2
*/
public static boolean hasValueClassProperty(Class> type) {
diff --git a/src/main/java/org/springframework/data/util/Predicates.java b/src/main/java/org/springframework/data/util/Predicates.java
index e9dcff34cd..885eda0868 100644
--- a/src/main/java/org/springframework/data/util/Predicates.java
+++ b/src/main/java/org/springframework/data/util/Predicates.java
@@ -60,18 +60,18 @@ static Predicate declaringClass(Predicate> predic
}
/**
- * A {@link Predicate} that yields always {@code true}.
+ * A {@link Predicate} that yields always {@literal true}.
*
- * @return a {@link Predicate} that yields always {@code true}.
+ * @return a {@link Predicate} that yields always {@literal true}.
*/
static Predicate isTrue() {
return t -> true;
}
/**
- * A {@link Predicate} that yields always {@code false}.
+ * A {@link Predicate} that yields always {@literal false}.
*
- * @return a {@link Predicate} that yields always {@code false}.
+ * @return a {@link Predicate} that yields always {@literal false}.
*/
static Predicate isFalse() {
return t -> false;
diff --git a/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java b/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java
index a38b907a8a..459a6999d4 100644
--- a/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/OffsetScrollPositionArgumentResolver.java
@@ -43,7 +43,7 @@ public interface OffsetScrollPositionArgumentResolver extends HandlerMethodArgum
* wrapped arguments in {@link java.util.Optional}.
*
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
- * {@link #supportsParameter} which must have returned {@code true}.
+ * {@link #supportsParameter} which must have returned {@literal true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
diff --git a/src/main/java/org/springframework/data/web/PageableArgumentResolver.java b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java
index b99dd2a783..c8885dae14 100644
--- a/src/main/java/org/springframework/data/web/PageableArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/PageableArgumentResolver.java
@@ -41,7 +41,7 @@ public interface PageableArgumentResolver extends HandlerMethodArgumentResolver
* Resolves a {@link Pageable} method parameter into an argument value from a given request.
*
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
- * {@link #supportsParameter} which must have returned {@code true}.
+ * {@link #supportsParameter} which must have returned {@literal true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
diff --git a/src/main/java/org/springframework/data/web/SortArgumentResolver.java b/src/main/java/org/springframework/data/web/SortArgumentResolver.java
index ee9e1f8873..60f0554e02 100644
--- a/src/main/java/org/springframework/data/web/SortArgumentResolver.java
+++ b/src/main/java/org/springframework/data/web/SortArgumentResolver.java
@@ -41,7 +41,7 @@ public interface SortArgumentResolver extends HandlerMethodArgumentResolver {
* Resolves a {@link Sort} method parameter into an argument value from a given request.
*
* @param parameter the method parameter to resolve. This parameter must have previously been passed to
- * {@link #supportsParameter} which must have returned {@code true}.
+ * {@link #supportsParameter} which must have returned {@literal true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
diff --git a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java
index c1b9addcfa..940f46429a 100755
--- a/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java
+++ b/src/test/java/org/springframework/data/auditing/AuditingHandlerUnitTests.java
@@ -96,7 +96,7 @@ void setsAuditorIfConfigured() {
}
/**
- * Checks that the advice does not set modification information on creation if the falg is set to {@code false}.
+ * Checks that the advice does not set modification information on creation if the falg is set to {@literal false}.
*/
@Test
void honoursModifiedOnCreationFlag() {
diff --git a/src/test/java/org/springframework/data/convert/DtoInstantiatingConverterUnitTests.java b/src/test/java/org/springframework/data/convert/DtoInstantiatingConverterUnitTests.java
index a511b3beb3..5fce08844c 100644
--- a/src/test/java/org/springframework/data/convert/DtoInstantiatingConverterUnitTests.java
+++ b/src/test/java/org/springframework/data/convert/DtoInstantiatingConverterUnitTests.java
@@ -20,6 +20,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Reference;
+import org.springframework.data.annotation.Transient;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.model.EntityInstantiators;
@@ -27,10 +28,11 @@
* Unit tests for {@link DtoInstantiatingConverter}.
*
* @author Mark Paluch
+ * @author Christoph Strobl
*/
class DtoInstantiatingConverterUnitTests {
- @Test // GH- 3104
+ @Test // GH-3104
void dtoProjectionShouldConsiderPropertiesAndAssociations() {
TheOtherThing ref = new TheOtherThing();
@@ -44,6 +46,20 @@ void dtoProjectionShouldConsiderPropertiesAndAssociations() {
assertThat(projection.ref).isSameAs(ref);
}
+ @Test // GH-2942
+ void shouldDefaultTransientConstructorParameterWhenConvertingToDto() {
+
+ SourceEntity source = new SourceEntity("1", "Alice");
+ DtoInstantiatingConverter converter = new DtoInstantiatingConverter(DtoWithTransientParam.class,
+ new SampleMappingContext(), new EntityInstantiators());
+
+ DtoWithTransientParam dto = (DtoWithTransientParam) converter.convert(source);
+
+ assertThat(dto.id).isEqualTo("1");
+ assertThat(dto.name).isEqualTo("Alice");
+ assertThat(dto.summary).isNull();
+ }
+
static class MyAssociativeEntity {
String id;
@@ -66,6 +82,19 @@ static class DtoProjection {
@Reference TheOtherThing ref;
}
+ static class SourceEntity {
+
+ String id;
+ String name;
+
+ public SourceEntity(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+ }
+
+ record DtoWithTransientParam(String id, String name, @Transient String summary) {}
+
static class TheOtherThing {
String id;
}
diff --git a/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java b/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java
index 9fe20fa06a..c6d9d39353 100644
--- a/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java
+++ b/src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java
@@ -18,6 +18,7 @@
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
+import org.springframework.data.annotation.Transient;
import org.springframework.data.mapping.context.SampleMappingContext;
import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.mapping.model.EntityInstantiators;
@@ -28,6 +29,7 @@
*
* @author Oliver Drotbohm
* @author Mark Paluch
+ * @author Christoph Strobl
*/
class InstantiationAwarePersistentPropertyAccessorUnitTests {
@@ -88,6 +90,32 @@ void shouldSetPropertyOfRecordUsingCanonicalConstructor() {
assertThat(wrapper.getBean()).isEqualTo(new WithSingleArgConstructor(41L, "Oliver August"));
}
+ /**
+ * Reproduces failure: when an entity has both persistent and transient constructor parameters, setting a
+ * persistent property via the copy path (InstantiationAwarePropertyAccessor) should succeed and leave the
+ * transient parameter at its default (null). Currently throws IllegalStateException because
+ * getRequiredPersistentProperty(transientParamName) is used for constructor arguments.
+ */
+ @Test // GH-2942
+ void shouldSetPersistentPropertyWhenEntityHasTransientConstructorParameter() {
+
+ var instantiators = new EntityInstantiators();
+ var context = new SampleMappingContext();
+
+ PersistentEntity entity = context
+ .getRequiredPersistentEntity(RecordWithPersistentAndTransientParams.class);
+
+ var bean = new RecordWithPersistentAndTransientParams(42L, "Alice", null);
+
+ PersistentPropertyAccessor wrapper = new InstantiationAwarePropertyAccessor<>(
+ bean, entity::getPropertyAccessor, instantiators);
+
+ wrapper.setProperty(entity.getRequiredPersistentProperty("name"), "Bob");
+ wrapper.setProperty(entity.getRequiredPersistentProperty("id"), 42L);
+
+ assertThat(wrapper.getBean()).isEqualTo(new RecordWithPersistentAndTransientParams(42L, "Bob", null));
+ }
+
record Sample(String firstname, String lastname, int age) {
}
@@ -99,4 +127,5 @@ public WithSingleArgConstructor(String name) {
}
}
+ record RecordWithPersistentAndTransientParams(Long id, String name, @Transient String displayName) {}
}
diff --git a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
index c3c3e03d01..52ee422855 100755
--- a/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
+++ b/src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java
@@ -32,6 +32,8 @@
import org.jspecify.annotations.Nullable;
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;
@@ -106,8 +108,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");
@@ -199,8 +200,8 @@ void returnsGeneratedPropertyAccessorForPropertyAccessor() {
assertThat(accessor).isNotInstanceOf(BeanWrapper.class);
assertThat(accessor).isInstanceOfSatisfying(InstantiationAwarePropertyAccessor.class, it -> {
- var delegateFunction = (Function>) ReflectionTestUtils
- .getField(it, "delegateFunction");
+ var delegateFunction = (Function>) ReflectionTestUtils.getField(it,
+ "delegateFunction");
var delegate = delegateFunction.apply(value);
assertThat(delegate.getClass().getName()).contains("_Accessor_");
@@ -360,6 +361,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 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() {
@@ -476,6 +489,17 @@ 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 {
@@ -483,4 +507,5 @@ static class WithAssociation {
String property;
@Reference WithAssociation association;
}
+
}
diff --git a/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorIntegrationTests.java b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorIntegrationTests.java
new file mode 100644
index 0000000000..57cc6ffce0
--- /dev/null
+++ b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorIntegrationTests.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mapping.model;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.data.annotation.Transient;
+import org.springframework.data.mapping.context.SampleMappingContext;
+import org.springframework.data.mapping.context.SamplePersistentProperty;
+
+/**
+ * Integration tests for {@link EntityInstantiator}.
+ *
+ * @author Mark Paluch
+ */
+public class EntityInstantiatorIntegrationTests {
+
+ SampleMappingContext context = new SampleMappingContext();
+ EntityInstantiators instantiators = new EntityInstantiators();
+
+ @Test // GH-2942
+ void shouldDefaultTransientProperties() {
+
+ WithTransientProperty instance = createInstance(WithTransientProperty.class);
+
+ assertThat(instance.foo).isEqualTo(null);
+ assertThat(instance.bar).isEqualTo(0);
+ }
+
+ @Test // GH-2942
+ void shouldDefaultTransientRecordProperties() {
+
+ RecordWithTransientProperty instance = createInstance(RecordWithTransientProperty.class);
+
+ assertThat(instance.foo).isEqualTo(null);
+ assertThat(instance.bar).isEqualTo(0);
+ }
+
+ @Test // GH-2942
+ void shouldDefaultTransientKotlinProperty() {
+
+ DataClassWithTransientProperties instance = createInstance(DataClassWithTransientProperties.class);
+
+ // Kotlin defaulting
+ assertThat(instance.getFoo()).isEqualTo("foo");
+
+ // Our defaulting
+ assertThat(instance.getBar()).isEqualTo(0);
+ }
+
+ @SuppressWarnings("unchecked")
+ private E createInstance(Class entityType) {
+
+ var entity = context.getRequiredPersistentEntity(entityType);
+ var instantiator = instantiators.getInstantiatorFor(entity);
+
+ return (E) instantiator.createInstance(entity,
+ new PersistentEntityParameterValueProvider<>(entity, new PropertyValueProvider() {
+ @Override
+ public T getPropertyValue(SamplePersistentProperty property) {
+ return null;
+ }
+ }, null));
+ }
+
+ static class WithTransientProperty {
+
+ @Transient String foo;
+ @Transient int bar;
+
+ public WithTransientProperty(String foo, int bar) {
+
+ }
+ }
+
+ record RecordWithTransientProperty(@Transient String foo, @Transient int bar) {
+
+ }
+
+}
diff --git a/src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt b/src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt
index 4479c6c0a1..7cc04451d3 100644
--- a/src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt
+++ b/src/test/kotlin/org/springframework/data/mapping/model/DataClasses.kt
@@ -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
/**
@@ -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,