Browse Source

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.
pull/460/head
Mark Paluch 6 years ago
parent
commit
ebcea509b8
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 39
      src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java
  2. 8
      src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessorFactory.java
  3. 28
      src/test/java/org/springframework/data/mapping/InstantiationAwarePersistentPropertyAccessorUnitTests.java
  4. 9
      src/test/java/org/springframework/data/mapping/model/BasicPersistentEntityUnitTests.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
*/
package org.springframework.data.mapping.model;
import java.util.function.Function;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@ -31,13 +33,15 @@ import org.springframework.util.Assert; @@ -31,13 +33,15 @@ import org.springframework.util.Assert;
* {@link PersistentProperty} is to be applied on a completely immutable entity type exposing a persistence constructor.
*
* @author Oliver Drotbohm
* @author Mark Paluch
* @since 2.3
*/
public class InstantiationAwarePropertyAccessor<T> implements PersistentPropertyAccessor<T> {
private static final String NO_SETTER_OR_CONSTRUCTOR = "Cannot set property %s because no setter, wither or copy constructor exists for %s!";
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!";
private final PersistentPropertyAccessor<T> delegate;
private final Function<T, PersistentPropertyAccessor<T>> delegateFunction;
private final EntityInstantiators instantiators;
private T bean;
@ -48,17 +52,41 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty @@ -48,17 +52,41 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
*
* @param delegate must not be {@literal null}.
* @param instantiators must not be {@literal null}.
* @deprecated since 2.4. Using this constructor allows only setting a single property as
* {@link PersistentPropertyAccessor} holds a reference to the initial bean state.
*/
@Deprecated
public InstantiationAwarePropertyAccessor(PersistentPropertyAccessor<T> delegate, EntityInstantiators instantiators) {
Assert.notNull(delegate, "Delegate PersistenPropertyAccessor must not be null!");
Assert.notNull(delegate, "Delegate PersistentPropertyAccessor must not be null!");
Assert.notNull(instantiators, "EntityInstantiators must not be null!");
this.delegate = delegate;
this.instantiators = instantiators;
this.delegateFunction = t -> delegate;
this.bean = delegate.getBean();
}
/**
* Creates an {@link InstantiationAwarePropertyAccessor} using the given delegate {@code accessorFunction} and
* {@link EntityInstantiators}. {@code accessorFunction} is used to obtain a new {@link PersistentPropertyAccessor}
* for each property to set.
*
* @param bean must not be {@literal null}.
* @param accessorFunction must not be {@literal null}.
* @param instantiators must not be {@literal null}.
*/
public InstantiationAwarePropertyAccessor(T bean, Function<T, PersistentPropertyAccessor<T>> accessorFunction,
EntityInstantiators instantiators) {
Assert.notNull(bean, "Bean must not be null!");
Assert.notNull(accessorFunction, "PersistentPropertyAccessor function must not be null!");
Assert.notNull(instantiators, "EntityInstantiators must not be null!");
this.delegateFunction = accessorFunction;
this.instantiators = instantiators;
this.bean = bean;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.lang.Object)
@ -68,6 +96,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty @@ -68,6 +96,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
public void setProperty(PersistentProperty<?> property, @Nullable Object value) {
PersistentEntity<?, ?> owner = property.getOwner();
PersistentPropertyAccessor<T> delegate = delegateFunction.apply(this.bean);
if (!property.isImmutable() || property.getWither() != null || ReflectionUtils.isKotlinClass(owner.getType())) {
@ -123,7 +152,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty @@ -123,7 +152,7 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
@Nullable
@Override
public Object getProperty(PersistentProperty<?> property) {
return delegate.getProperty(property);
return delegateFunction.apply(bean).getProperty(property);
}
/*

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

@ -23,6 +23,8 @@ import org.springframework.data.mapping.PersistentPropertyAccessor; @@ -23,6 +23,8 @@ import org.springframework.data.mapping.PersistentPropertyAccessor;
* an {@link InstantiationAwarePropertyAccessor} to allow the handling of purely immutable types.
*
* @author Oliver Drotbohm
* @author Mark Paluch
* @since 2.3
*/
public class InstantiationAwarePropertyAccessorFactory implements PersistentPropertyAccessorFactory {
@ -41,10 +43,8 @@ public class InstantiationAwarePropertyAccessorFactory implements PersistentProp @@ -41,10 +43,8 @@ public class InstantiationAwarePropertyAccessorFactory implements PersistentProp
*/
@Override
public <T> PersistentPropertyAccessor<T> getPropertyAccessor(PersistentEntity<?, ?> entity, T bean) {
PersistentPropertyAccessor<T> accessor = delegate.getPropertyAccessor(entity, bean);
return new InstantiationAwarePropertyAccessor<>(accessor, instantiators);
return new InstantiationAwarePropertyAccessor<>(bean, it -> delegate.getPropertyAccessor(entity, it),
instantiators);
}
/*

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@ -26,12 +26,15 @@ import org.springframework.data.mapping.context.SamplePersistentProperty; @@ -26,12 +26,15 @@ import org.springframework.data.mapping.context.SamplePersistentProperty;
import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor;
/**
* Unit tests for {@link InstantiationAwarePropertyAccessor}.
*
* @author Oliver Drotbohm
* @author Mark Paluch
*/
public class InstantiationAwarePersistentPropertyAccessorUnitTests {
@Test
public void testname() {
@Test // DATACMNS-1639
public void shouldCreateNewInstance() {
EntityInstantiators instantiators = new EntityInstantiators();
SampleMappingContext context = new SampleMappingContext();
@ -48,6 +51,25 @@ public class InstantiationAwarePersistentPropertyAccessorUnitTests { @@ -48,6 +51,25 @@ public class InstantiationAwarePersistentPropertyAccessorUnitTests {
assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Matthews", 42));
}
@Test // DATACMNS-1768
public void shouldSetMultipleProperties() {
EntityInstantiators instantiators = new EntityInstantiators();
SampleMappingContext context = new SampleMappingContext();
PersistentEntity<Object, SamplePersistentProperty> entity = context.getRequiredPersistentEntity(Sample.class);
Sample bean = new Sample("Dave", "Matthews", 42);
PersistentPropertyAccessor<Sample> wrapper = new InstantiationAwarePropertyAccessor<>(bean,
entity::getPropertyAccessor, instantiators);
wrapper.setProperty(entity.getRequiredPersistentProperty("firstname"), "Oliver August");
wrapper.setProperty(entity.getRequiredPersistentProperty("lastname"), "Heisenberg");
assertThat(wrapper.getBean()).isEqualTo(new Sample("Oliver August", "Heisenberg", 42));
}
@Value
static class Sample {

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

@ -28,6 +28,7 @@ import java.util.Iterator; @@ -28,6 +28,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@ -186,7 +187,8 @@ class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> { @@ -186,7 +187,8 @@ class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
.hasMessageContaining("Required property foo not found");
}
@Test // DATACMNS-809
@Test // DATACMNS-809, DATACMNS-1768
@SuppressWarnings("rawtypes")
void returnsGeneratedPropertyAccessorForPropertyAccessor() {
SampleMappingContext context = new SampleMappingContext();
@ -196,11 +198,12 @@ class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> { @@ -196,11 +198,12 @@ class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value);
assertThat(accessor).isNotInstanceOf(BeanWrapper.class);
assertThat(accessor).isInstanceOfSatisfying(InstantiationAwarePropertyAccessor.class, it -> {
PersistentPropertyAccessor delegate = (PersistentPropertyAccessor) ReflectionTestUtils.getField(it, "delegate");
Function<Object, PersistentPropertyAccessor<Object>> delegateFunction = (Function<Object, PersistentPropertyAccessor<Object>>) ReflectionTestUtils
.getField(it, "delegateFunction");
PersistentPropertyAccessor<Object> delegate = delegateFunction.apply(value);
assertThat(delegate.getClass().getName()).contains("_Accessor_");
assertThat(delegate.getBean()).isEqualTo(value);
});

Loading…
Cancel
Save