Skip to content

Commit ebcea50

Browse files
committed
DATACMNS-1768 - Reuse bean state in InstantiationAwarePropertyAccessor when setting multiple properties.
We now reuse the new bean in InstantiationAwarePropertyAccessor when setting properties. Previously, we used the initial bean state as the bean was held by a delegate PersistentPropertyAccessor which caused only the last set property to be visible.
1 parent f6630a5 commit ebcea50

File tree

4 files changed

+69
-15
lines changed

4 files changed

+69
-15
lines changed

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

+34-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original author or authors.
2+
* Copyright 2019-2020 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.
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.mapping.model;
1717

18+
import java.util.function.Function;
19+
1820
import org.springframework.data.annotation.PersistenceConstructor;
1921
import org.springframework.data.mapping.PersistentEntity;
2022
import org.springframework.data.mapping.PersistentProperty;
@@ -31,13 +33,15 @@
3133
* {@link PersistentProperty} is to be applied on a completely immutable entity type exposing a persistence constructor.
3234
*
3335
* @author Oliver Drotbohm
36+
* @author Mark Paluch
37+
* @since 2.3
3438
*/
3539
public class InstantiationAwarePropertyAccessor<T> implements PersistentPropertyAccessor<T> {
3640

3741
private static final String NO_SETTER_OR_CONSTRUCTOR = "Cannot set property %s because no setter, wither or copy constructor exists for %s!";
3842
private static final String NO_CONSTRUCTOR_PARAMETER = "Cannot set property %s because no setter, no wither and it's not part of the persistence constructor %s!";
3943

40-
private final PersistentPropertyAccessor<T> delegate;
44+
private final Function<T, PersistentPropertyAccessor<T>> delegateFunction;
4145
private final EntityInstantiators instantiators;
4246

4347
private T bean;
@@ -48,17 +52,41 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
4852
*
4953
* @param delegate must not be {@literal null}.
5054
* @param instantiators must not be {@literal null}.
55+
* @deprecated since 2.4. Using this constructor allows only setting a single property as
56+
* {@link PersistentPropertyAccessor} holds a reference to the initial bean state.
5157
*/
58+
@Deprecated
5259
public InstantiationAwarePropertyAccessor(PersistentPropertyAccessor<T> delegate, EntityInstantiators instantiators) {
5360

54-
Assert.notNull(delegate, "Delegate PersistenPropertyAccessor must not be null!");
61+
Assert.notNull(delegate, "Delegate PersistentPropertyAccessor must not be null!");
5562
Assert.notNull(instantiators, "EntityInstantiators must not be null!");
5663

57-
this.delegate = delegate;
5864
this.instantiators = instantiators;
65+
this.delegateFunction = t -> delegate;
5966
this.bean = delegate.getBean();
6067
}
6168

69+
/**
70+
* Creates an {@link InstantiationAwarePropertyAccessor} using the given delegate {@code accessorFunction} and
71+
* {@link EntityInstantiators}. {@code accessorFunction} is used to obtain a new {@link PersistentPropertyAccessor}
72+
* for each property to set.
73+
*
74+
* @param bean must not be {@literal null}.
75+
* @param accessorFunction must not be {@literal null}.
76+
* @param instantiators must not be {@literal null}.
77+
*/
78+
public InstantiationAwarePropertyAccessor(T bean, Function<T, PersistentPropertyAccessor<T>> accessorFunction,
79+
EntityInstantiators instantiators) {
80+
81+
Assert.notNull(bean, "Bean must not be null!");
82+
Assert.notNull(accessorFunction, "PersistentPropertyAccessor function must not be null!");
83+
Assert.notNull(instantiators, "EntityInstantiators must not be null!");
84+
85+
this.delegateFunction = accessorFunction;
86+
this.instantiators = instantiators;
87+
this.bean = bean;
88+
}
89+
6290
/*
6391
* (non-Javadoc)
6492
* @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object)
@@ -68,6 +96,7 @@ public InstantiationAwarePropertyAccessor(PersistentPropertyAccessor<T> delegate
6896
public void setProperty(PersistentProperty<?> property, @Nullable Object value) {
6997

7098
PersistentEntity<?, ?> owner = property.getOwner();
99+
PersistentPropertyAccessor<T> delegate = delegateFunction.apply(this.bean);
71100

72101
if (!property.isImmutable() || property.getWither() != null || ReflectionUtils.isKotlinClass(owner.getType())) {
73102

@@ -123,7 +152,7 @@ public Object getParameterValue(Parameter parameter) {
123152
@Nullable
124153
@Override
125154
public Object getProperty(PersistentProperty<?> property) {
126-
return delegate.getProperty(property);
155+
return delegateFunction.apply(bean).getProperty(property);
127156
}
128157

129158
/*

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
* an {@link InstantiationAwarePropertyAccessor} to allow the handling of purely immutable types.
2424
*
2525
* @author Oliver Drotbohm
26+
* @author Mark Paluch
27+
* @since 2.3
2628
*/
2729
public class InstantiationAwarePropertyAccessorFactory implements PersistentPropertyAccessorFactory {
2830

@@ -41,10 +43,8 @@ public InstantiationAwarePropertyAccessorFactory(PersistentPropertyAccessorFacto
4143
*/
4244
@Override
4345
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
44-
45-
PersistentPropertyAccessor<T> accessor = delegate.getPropertyAccessor(entity, bean);
46-
47-
return new InstantiationAwarePropertyAccessor<>(accessor, instantiators);
46+
return new InstantiationAwarePropertyAccessor<>(bean, it -> delegate.getPropertyAccessor(entity, it),
47+
instantiators);
4848
}
4949

5050
/*

src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original author or authors.
2+
* Copyright 2019-2020 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.
@@ -26,12 +26,15 @@
2626
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor;
2727

2828
/**
29+
* Unit tests for {@link InstantiationAwarePropertyAccessor}.
30+
*
2931
* @author Oliver Drotbohm
32+
* @author Mark Paluch
3033
*/
3134
public class InstantiationAwarePersistentPropertyAccessorUnitTests {
3235

33-
@Test
34-
public void testname() {
36+
@Test // DATACMNS-1639
37+
public void shouldCreateNewInstance() {
3538

3639
EntityInstantiators instantiators = new EntityInstantiators();
3740
SampleMappingContext context = new SampleMappingContext();
@@ -48,6 +51,25 @@ public void testname() {
4851
assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Matthews", 42));
4952
}
5053

54+
@Test // DATACMNS-1768
55+
public void shouldSetMultipleProperties() {
56+
57+
EntityInstantiators instantiators = new EntityInstantiators();
58+
SampleMappingContext context = new SampleMappingContext();
59+
60+
PersistentEntity<Object, SamplePersistentProperty> entity = context.getRequiredPersistentEntity(Sample.class);
61+
62+
Sample bean = new Sample("Dave", "Matthews", 42);
63+
64+
PersistentPropertyAccessor<Sample> wrapper = new InstantiationAwarePropertyAccessor<>(bean,
65+
entity::getPropertyAccessor, instantiators);
66+
67+
wrapper.setProperty(entity.getRequiredPersistentProperty("firstname"), "Oliver August");
68+
wrapper.setProperty(entity.getRequiredPersistentProperty("lastname"), "Heisenberg");
69+
70+
assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Heisenberg", 42));
71+
}
72+
5173
@Value
5274
static class Sample {
5375

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.List;
2929
import java.util.concurrent.CountDownLatch;
3030
import java.util.concurrent.atomic.AtomicBoolean;
31+
import java.util.function.Function;
3132
import java.util.stream.Stream;
3233

3334
import org.junit.jupiter.api.Test;
@@ -186,7 +187,8 @@ void reportsRequiredPropertyName() {
186187
.hasMessageContaining("Required property foo not found");
187188
}
188189

189-
@Test // DATACMNS-809
190+
@Test // DATACMNS-809, DATACMNS-1768
191+
@SuppressWarnings("rawtypes")
190192
void returnsGeneratedPropertyAccessorForPropertyAccessor() {
191193

192194
SampleMappingContext context = new SampleMappingContext();
@@ -196,11 +198,12 @@ void returnsGeneratedPropertyAccessorForPropertyAccessor() {
196198
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value);
197199

198200
assertThat(accessor).isNotInstanceOf(BeanWrapper.class);
199-
200201
assertThat(accessor).isInstanceOfSatisfying(InstantiationAwarePropertyAccessor.class, it -> {
201202

202-
PersistentPropertyAccessor delegate = (PersistentPropertyAccessor) ReflectionTestUtils.getField(it, "delegate");
203+
Function<Object, PersistentPropertyAccessor<Object>> delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils
204+
.getField(it, "delegateFunction");
203205

206+
PersistentPropertyAccessor<Object> delegate = delegateFunction.apply(value);
204207
assertThat(delegate.getClass().getName()).contains("_Accessor_");
205208
assertThat(delegate.getBean()).isEqualTo(value);
206209
});

0 commit comments

Comments
 (0)