Don't try to write non-writeable properties.

Original Pull Request #2249
Closes #2230

(cherry picked from commit acf02a1dc9e427b1d72bd949553e70f51539bd4d)

# Conflicts:
#	src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java
This commit is contained in:
Peter-Josef Meisch 2022-08-04 08:04:19 +02:00
parent be70a398be
commit 3c6d96e49f
7 changed files with 200 additions and 74 deletions

View File

@ -478,7 +478,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings! // Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) { && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
} }

View File

@ -259,7 +259,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings! // Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isWritable()
&& idProperty.getType().isAssignableFrom(String.class)) { && idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
} }

View File

@ -15,12 +15,6 @@
*/ */
package org.springframework.data.elasticsearch.core.convert; package org.springframework.data.elasticsearch.core.convert;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@ -53,7 +47,16 @@ import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.*; import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.EntityInstantiator;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar; import org.springframework.format.datetime.DateFormatterRegistrar;
@ -63,6 +66,12 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/** /**
* Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type * Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type
* {@link ElasticsearchPersistentEntity metadata}. * {@link ElasticsearchPersistentEntity metadata}.
@ -83,12 +92,15 @@ import org.springframework.util.ObjectUtils;
public class MappingElasticsearchConverter public class MappingElasticsearchConverter
implements ElasticsearchConverter, ApplicationContextAware, InitializingBean { implements ElasticsearchConverter, ApplicationContextAware, InitializingBean {
private static final String INCOMPATIBLE_TYPES = "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions."; private static final String INCOMPATIBLE_TYPES =
private static final String INVALID_TYPE_TO_READ = "Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!"; "Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions.";
private static final String INVALID_TYPE_TO_READ =
"Expected to read Document %s into type %s but didn't find a PersistentEntity for the latter!";
private static final Log LOGGER = LogFactory.getLog(MappingElasticsearchConverter.class); private static final Log LOGGER = LogFactory.getLog(MappingElasticsearchConverter.class);
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext; private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>
mappingContext;
private final GenericConversionService conversionService; private final GenericConversionService conversionService;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList()); private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
private final SpELContext spELContext = new SpELContext(new MapAccessor()); private final SpELContext spELContext = new SpELContext(new MapAccessor());
@ -130,8 +142,8 @@ public class MappingElasticsearchConverter
} }
/** /**
* Set the {@link CustomConversions} to be applied during the mapping process. <br /> * Set the {@link CustomConversions} to be applied during the mapping process. <br /> Conversions are registered
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}. * after {@link #afterPropertiesSet() bean initialization}.
* *
* @param conversions must not be {@literal null}. * @param conversions must not be {@literal null}.
*/ */
@ -157,7 +169,8 @@ public class MappingElasticsearchConverter
@Override @Override
public <R> R read(Class<R> type, Document source) { public <R> R read(Class<R> type, Document source) {
Reader reader = new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators); Reader reader =
new Reader(mappingContext, conversionService, conversions, typeMapper, spELContext, instantiators);
return reader.read(type, source); return reader.read(type, source);
} }
@ -175,7 +188,8 @@ public class MappingElasticsearchConverter
*/ */
private static class Base { private static class Base {
protected final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext; protected final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty>
mappingContext;
protected final ElasticsearchTypeMapper typeMapper; protected final ElasticsearchTypeMapper typeMapper;
protected final GenericConversionService conversionService; protected final GenericConversionService conversionService;
protected final CustomConversions conversions; protected final CustomConversions conversions;
@ -183,7 +197,8 @@ public class MappingElasticsearchConverter
private Base( private Base(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper) { GenericConversionService conversionService, CustomConversions conversions,
ElasticsearchTypeMapper typeMapper) {
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.conversionService = conversionService; this.conversionService = conversionService;
this.conversions = conversions; this.conversions = conversions;
@ -202,7 +217,8 @@ public class MappingElasticsearchConverter
public Reader( public Reader(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper, GenericConversionService conversionService, CustomConversions conversions,
ElasticsearchTypeMapper typeMapper,
SpELContext spELContext, EntityInstantiators instantiators) { SpELContext spELContext, EntityInstantiators instantiators) {
super(mappingContext, conversionService, conversions, typeMapper); super(mappingContext, conversionService, conversions, typeMapper);
@ -292,7 +308,8 @@ public class MappingElasticsearchConverter
map.put(key, read(defaultedValueType, (Map<String, Object>) value)); map.put(key, read(defaultedValueType, (Map<String, Object>) value));
} else if (value instanceof List) { } else if (value instanceof List) {
map.put(key, map.put(key,
readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List<Object>) value)); readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST,
(List<Object>) value));
} else { } else {
map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType)); map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType));
} }
@ -316,14 +333,15 @@ public class MappingElasticsearchConverter
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity); EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
@SuppressWarnings({ "unchecked" }) @SuppressWarnings({"unchecked"})
R instance = (R) instantiator.createInstance(targetEntity, propertyValueProvider); R instance = (R) instantiator.createInstance(targetEntity, propertyValueProvider);
if (!targetEntity.requiresPropertyPopulation()) { if (!targetEntity.requiresPropertyPopulation()) {
return instance; return instance;
} }
ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator); ElasticsearchPropertyValueProvider valueProvider =
new ElasticsearchPropertyValueProvider(accessor, evaluator);
R result = readProperties(targetEntity, instance, valueProvider); R result = readProperties(targetEntity, instance, valueProvider);
if (source instanceof Document) { if (source instanceof Document) {
@ -333,7 +351,8 @@ public class MappingElasticsearchConverter
PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>( PersistentPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
targetEntity.getPropertyAccessor(result), conversionService); targetEntity.getPropertyAccessor(result), conversionService);
// Only deal with String because ES generated Ids are strings ! // Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { if (idProperty != null && idProperty.isWritable() &&
idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, document.getId()); propertyAccessor.setProperty(idProperty, document.getId());
} }
} }
@ -352,7 +371,8 @@ public class MappingElasticsearchConverter
if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) { if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) {
if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) { if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) {
SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm()); SeqNoPrimaryTerm seqNoPrimaryTerm =
new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm());
ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty(); ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty();
targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm); targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm);
} }
@ -375,10 +395,12 @@ public class MappingElasticsearchConverter
// TODO: Support for non-static inner classes via ObjectPath // TODO: Support for non-static inner classes via ObjectPath
// noinspection ConstantConditions // noinspection ConstantConditions
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>( PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider =
new PersistentEntityParameterValueProvider<>(
entity, provider, null); entity, provider, null);
return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider); return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService,
parameterProvider);
} }
private boolean isAssignedSeqNo(long seqNo) { private boolean isAssignedSeqNo(long seqNo) {
@ -392,12 +414,13 @@ public class MappingElasticsearchConverter
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance, protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
ElasticsearchPropertyValueProvider valueProvider) { ElasticsearchPropertyValueProvider valueProvider) {
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance), PersistentPropertyAccessor<R> accessor =
new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService); conversionService);
for (ElasticsearchPersistentProperty prop : entity) { for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || !prop.isReadable()) { if (entity.isConstructorArgument(prop) || !prop.isReadable() || !prop.isWritable()) {
continue; continue;
} }
@ -462,7 +485,8 @@ public class MappingElasticsearchConverter
} }
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) { private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter()); PropertyValueConverter propertyValueConverter =
Objects.requireNonNull(property.getPropertyValueConverter());
if (source instanceof String[]) { if (source instanceof String[]) {
// convert to a List // convert to a List
@ -543,7 +567,7 @@ public class MappingElasticsearchConverter
return getPotentiallyConvertedSimpleRead(value, targetType.getType()); return getPotentiallyConvertedSimpleRead(value, targetType.getType());
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({"unchecked", "rawtypes"})
@Nullable @Nullable
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) { private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
@ -639,7 +663,8 @@ public class MappingElasticsearchConverter
* @param delegate must not be {@literal null}. * @param delegate must not be {@literal null}.
*/ */
public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator,
ConversionService conversionService, ParameterValueProvider<ElasticsearchPersistentProperty> delegate) { ConversionService conversionService,
ParameterValueProvider<ElasticsearchPersistentProperty> delegate) {
super(evaluator, conversionService, delegate); super(evaluator, conversionService, delegate);
} }
@ -649,7 +674,8 @@ public class MappingElasticsearchConverter
* @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter) * @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter)
*/ */
@Override @Override
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, ElasticsearchPersistentProperty> parameter) { protected <T> T potentiallyConvertSpelValue(Object object,
Parameter<T, ElasticsearchPersistentProperty> parameter) {
return readValue(object, parameter.getType()); return readValue(object, parameter.getType());
} }
} }
@ -675,7 +701,8 @@ public class MappingElasticsearchConverter
public Writer( public Writer(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
GenericConversionService conversionService, CustomConversions conversions, ElasticsearchTypeMapper typeMapper) { GenericConversionService conversionService, CustomConversions conversions,
ElasticsearchTypeMapper typeMapper) {
super(mappingContext, conversionService, conversions, typeMapper); super(mappingContext, conversionService, conversions, typeMapper);
} }
@ -760,7 +787,8 @@ public class MappingElasticsearchConverter
} }
if (null == entity) { if (null == entity) {
throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName()); throw new MappingException(
"No mapping metadata found for entity of type " + source.getClass().getName());
} }
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source); PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
@ -809,7 +837,8 @@ public class MappingElasticsearchConverter
sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class)); sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class));
} else if (value instanceof Collection || value.getClass().isArray()) { } else if (value instanceof Collection || value.getClass().isArray()) {
sink.put(simpleKey, sink.put(simpleKey,
writeCollectionInternal(asCollection(value), propertyType.getMapValueType(), new ArrayList<>())); writeCollectionInternal(asCollection(value), propertyType.getMapValueType(),
new ArrayList<>()));
} else { } else {
Map<String, Object> document = Document.create(); Map<String, Object> document = Document.create();
TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType() TypeInformation<?> valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType()
@ -961,9 +990,9 @@ public class MappingElasticsearchConverter
} }
/** /**
* Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the * Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not
* same as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of * the same as the one given. This is usually the case if you store a subtype of the actual declared
* the property. * typeInformation of the property.
* *
* @param source must not be {@literal null}. * @param source must not be {@literal null}.
* @param sink must not be {@literal null}. * @param sink must not be {@literal null}.
@ -1015,9 +1044,9 @@ public class MappingElasticsearchConverter
} }
/** /**
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch * Checks whether we have a custom conversion registered for the given value into an arbitrary simple
* type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as * Elasticsearch type. Returns the converted value if so. If not, we perform special enum handling or simply
* is. * return the value as is.
* *
* @param value value to convert * @param value value to convert
*/ */
@ -1057,7 +1086,8 @@ public class MappingElasticsearchConverter
} }
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) { private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter()); PropertyValueConverter propertyValueConverter =
Objects.requireNonNull(property.getPropertyValueConverter());
if (value instanceof List) { if (value instanceof List) {
value = ((List<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toList()); value = ((List<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toList());
@ -1076,7 +1106,8 @@ public class MappingElasticsearchConverter
* @param property must not be {@literal null}. * @param property must not be {@literal null}.
*/ */
protected List<Object> createCollection(Collection<?> collection, ElasticsearchPersistentProperty property) { protected List<Object> createCollection(Collection<?> collection, ElasticsearchPersistentProperty property) {
return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size())); return writeCollectionInternal(collection, property.getTypeInformation(),
new ArrayList<>(collection.size()));
} }
/** /**
@ -1152,12 +1183,12 @@ public class MappingElasticsearchConverter
if (sourceFilter.getIncludes() != null) { if (sourceFilter.getIncludes() != null) {
includes = updateFieldNames(Arrays.asList(sourceFilter.getIncludes()), persistentEntity) includes = updateFieldNames(Arrays.asList(sourceFilter.getIncludes()), persistentEntity)
.toArray(new String[] {}); .toArray(new String[]{});
} }
if (sourceFilter.getExcludes() != null) { if (sourceFilter.getExcludes() != null) {
excludes = updateFieldNames(Arrays.asList(sourceFilter.getExcludes()), persistentEntity) excludes = updateFieldNames(Arrays.asList(sourceFilter.getExcludes()), persistentEntity)
.toArray(new String[] {}); .toArray(new String[]{});
} }
query.addSourceFilter(new FetchSourceFilter(includes, excludes)); query.addSourceFilter(new FetchSourceFilter(includes, excludes));

View File

@ -45,7 +45,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Version; import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@ -3718,6 +3720,21 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
} }
} }
@Test // #2230
@DisplayName("should work with readonly id")
void shouldWorkWithReadonlyId() {
ReadonlyIdEntity entity = new ReadonlyIdEntity();
entity.setPart1("foo");
entity.setPart2("bar");
operations.save(entity);
ReadonlyIdEntity readEntity = operations.get(entity.getId(), ReadonlyIdEntity.class);
assertThat(readEntity.getPart1()).isEqualTo(entity.getPart1());
assertThat(readEntity.getPart2()).isEqualTo(entity.getPart2());
}
@Document(indexName = "#{@indexNameProvider.indexName()}") @Document(indexName = "#{@indexNameProvider.indexName()}")
private static class SampleEntityUUIDKeyed { private static class SampleEntityUUIDKeyed {
@Nullable @Nullable
@ -4463,5 +4480,36 @@ public abstract class ElasticsearchIntegrationTests implements NewElasticsearchC
+ '}'; + '}';
} }
} }
@Document(indexName = "#{@indexNameProvider.indexName()}-readonly-id")
static class ReadonlyIdEntity {
@Field(type = FieldType.Keyword) private String part1;
@Field(type = FieldType.Keyword) private String part2;
@Id
@ReadOnlyProperty
@AccessType(AccessType.Type.PROPERTY)
public String getId() {
return part1 + '-' + part2;
}
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
}
// endregion // endregion
} }

View File

@ -49,7 +49,9 @@ import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Version; import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@ -1156,6 +1158,23 @@ public abstract class ReactiveElasticsearchIntegrationTests {
.verifyComplete(); .verifyComplete();
} }
@Test // #2230
@DisplayName("should work with readonly id")
void shouldWorkWithReadonlyId() {
ReadonlyIdEntity entity = new ReadonlyIdEntity();
entity.setPart1("foo");
entity.setPart2("bar");
operations.save(entity).block();
operations.get(entity.getId(), ReadonlyIdEntity.class) //
.as(StepVerifier::create) //
.assertNext(readEntity -> { //
assertThat(readEntity.getPart1()).isEqualTo(entity.getPart1()); //
assertThat(readEntity.getPart2()).isEqualTo(entity.getPart2()); //
}).verifyComplete();
}
// endregion // endregion
// region Helper functions // region Helper functions
@ -1489,5 +1508,35 @@ public abstract class ReactiveElasticsearchIntegrationTests {
+ seqNoPrimaryTerm + '}'; + seqNoPrimaryTerm + '}';
} }
} }
@Document(indexName = "#{@indexNameProvider.indexName()}-readonly-id")
static class ReadonlyIdEntity {
@Field(type = FieldType.Keyword) private String part1;
@Field(type = FieldType.Keyword) private String part2;
@Id
@ReadOnlyProperty
@AccessType(AccessType.Type.PROPERTY)
public String getId() {
return part1 + '-' + part2;
}
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
}
// endregion // endregion
} }

View File

@ -241,7 +241,7 @@ abstract class CallbackIntegrationTests {
@Id private String id; @Id private String id;
@Nullable private String text; @Nullable private String text;
@ReadOnlyProperty // @ReadOnlyProperty
@Nullable private String className; @Nullable private String className;
@Nullable @Nullable

View File

@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
@ -119,7 +118,6 @@ public abstract class ReactiveCallbackIntegrationTests {
@Id private String id; @Id private String id;
private String text; private String text;
@ReadOnlyProperty
@Nullable private String className; @Nullable private String className;
public SampleEntity(String id, String text) { public SampleEntity(String id, String text) {