Consider @Column and @Element annotated constructor parameters.

We now allow using Column and Element annotations for constructor arguments when using constructor creation from columns/UDT fields respective tuple value elements. The column name/element ordinal can be overwritten for persistent properties. Non-persistent properties can be easier obtained than through the Value("…") annotation that uses SpEL.

Closes #1104.
This commit is contained in:
Mark Paluch
2021-03-30 14:14:32 +02:00
parent 024214fbac
commit f75d692221
8 changed files with 725 additions and 72 deletions

View File

@@ -0,0 +1,291 @@
/*
* Copyright 2021 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.cassandra.core.convert;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.Element;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import com.datastax.oss.driver.api.core.CqlIdentifier;
/**
* {@link CassandraPersistentProperty} wrapper for a delegate {@link CassandraPersistentProperty} considering
* annotations from a constructor parameter.
*
* @author Mark Paluch
* @since 3.2
*/
class AnnotatedCassandraConstructorProperty implements CassandraPersistentProperty {
private final CassandraPersistentProperty delegate;
private final MergedAnnotations annotations;
public AnnotatedCassandraConstructorProperty(CassandraPersistentProperty delegate, MergedAnnotations annotations) {
this.delegate = delegate;
this.annotations = annotations;
}
@Override
@Nullable
public CqlIdentifier getColumnName() {
if (annotations.isPresent(Column.class)) {
return CqlIdentifier.fromCql(annotations.get(Column.class).getString("value"));
}
return delegate.getColumnName();
}
@Override
@Nullable
public Integer getOrdinal() {
if (annotations.isPresent(Element.class)) {
return annotations.get(Element.class).getInt("value");
}
return delegate.getOrdinal();
}
@Override
@Nullable
public Ordering getPrimaryKeyOrdering() {
return delegate.getPrimaryKeyOrdering();
}
@Override
public boolean isClusterKeyColumn() {
return delegate.isClusterKeyColumn();
}
@Override
public boolean isCompositePrimaryKey() {
return delegate.isCompositePrimaryKey();
}
@Override
public boolean isMapLike() {
return delegate.isMapLike();
}
@Override
public boolean isPartitionKeyColumn() {
return delegate.isPartitionKeyColumn();
}
@Override
public boolean isPrimaryKeyColumn() {
return delegate.isPrimaryKeyColumn();
}
@Override
@Nullable
public AnnotatedType findAnnotatedType(Class<? extends Annotation> annotationType) {
return delegate.findAnnotatedType(annotationType);
}
@Override
public PersistentEntity<?, CassandraPersistentProperty> getOwner() {
return delegate.getOwner();
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public Class<?> getType() {
return delegate.getType();
}
@Override
public TypeInformation<?> getTypeInformation() {
return delegate.getTypeInformation();
}
@Override
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
return delegate.getPersistentEntityTypes();
}
@Override
@Nullable
public Method getGetter() {
return delegate.getGetter();
}
@Override
@Nullable
public Method getSetter() {
return delegate.getSetter();
}
@Override
@Nullable
public Method getWither() {
return delegate.getWither();
}
@Override
@Nullable
public Field getField() {
return delegate.getField();
}
@Override
@Nullable
public String getSpelExpression() {
return delegate.getSpelExpression();
}
@Override
@Nullable
public Association<CassandraPersistentProperty> getAssociation() {
return delegate.getAssociation();
}
@Override
public boolean isEntity() {
return delegate.isEntity();
}
@Override
public boolean isIdProperty() {
return delegate.isIdProperty();
}
@Override
public boolean isVersionProperty() {
return delegate.isVersionProperty();
}
@Override
public boolean isCollectionLike() {
return delegate.isCollectionLike();
}
@Override
public boolean isMap() {
return delegate.isMap();
}
@Override
public boolean isArray() {
return delegate.isArray();
}
@Override
public boolean isTransient() {
return delegate.isTransient();
}
@Override
public boolean isWritable() {
return delegate.isWritable();
}
@Override
public boolean isImmutable() {
return delegate.isImmutable();
}
@Override
public boolean isAssociation() {
return delegate.isAssociation();
}
@Override
@Nullable
public Class<?> getComponentType() {
return delegate.getComponentType();
}
@Override
public Class<?> getRawType() {
return delegate.getRawType();
}
@Override
@Nullable
public Class<?> getMapValueType() {
return delegate.getMapValueType();
}
@Override
public Class<?> getActualType() {
return delegate.getActualType();
}
@Override
@Nullable
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return delegate.findAnnotation(annotationType);
}
@Override
@Nullable
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
return delegate.findPropertyOrOwnerAnnotation(annotationType);
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
return delegate.isAnnotationPresent(annotationType);
}
@Override
public boolean usePropertyAccess() {
return delegate.usePropertyAccess();
}
@Override
@Nullable
public Class<?> getAssociationTargetType() {
return delegate.getAssociationTargetType();
}
@Override
public void setColumnName(CqlIdentifier columnName) {
throw new UnsupportedOperationException();
}
@Override
@Deprecated
public void setForceQuote(boolean forceQuote) {
throw new UnsupportedOperationException();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,283 @@
/*
* Copyright 2021 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.cassandra.core.convert;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Optional;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import com.datastax.oss.driver.api.core.CqlIdentifier;
/**
* Annotated {@link CassandraPersistentProperty} synthesized from a constructor parameter.
*
* @author Mark Paluch
* @since 3.2
*/
class CassandraConstructorProperty implements CassandraPersistentProperty {
private final String name;
private final CassandraPersistentEntity<?> owner;
private final TypeInformation<?> typeInformation;
public CassandraConstructorProperty(String name, CassandraPersistentEntity<?> owner,
TypeInformation<?> typeInformation) {
this.name = name;
this.owner = owner;
this.typeInformation = typeInformation;
}
@Nullable
@Override
public CqlIdentifier getColumnName() {
throw new IllegalStateException(String.format("Parameter %s is not annotated with @Column", name));
}
@Nullable
@Override
public Integer getOrdinal() {
throw new IllegalStateException(String.format("Parameter %s is not annotated with @Element", name));
}
@Nullable
@Override
public Ordering getPrimaryKeyOrdering() {
throw new UnsupportedOperationException();
}
@Override
public boolean isClusterKeyColumn() {
return false;
}
@Override
public boolean isCompositePrimaryKey() {
return false;
}
@Override
public boolean isMapLike() {
return typeInformation.isMap();
}
@Override
public boolean isPartitionKeyColumn() {
return false;
}
@Override
public boolean isPrimaryKeyColumn() {
return false;
}
@Nullable
@Override
public AnnotatedType findAnnotatedType(Class<? extends Annotation> annotationType) {
return null;
}
@Override
public PersistentEntity<?, CassandraPersistentProperty> getOwner() {
return owner;
}
@Override
public String getName() {
return name;
}
@Override
public Class<?> getType() {
return null;
}
@Override
public TypeInformation<?> getTypeInformation() {
return typeInformation;
}
@Override
public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
return Collections.emptyList();
}
@Nullable
@Override
public Method getGetter() {
return null;
}
@Nullable
@Override
public Method getSetter() {
return null;
}
@Nullable
@Override
public Method getWither() {
return null;
}
@Nullable
@Override
public Field getField() {
return null;
}
@Nullable
@Override
public String getSpelExpression() {
return null;
}
@Nullable
@Override
public Association<CassandraPersistentProperty> getAssociation() {
return null;
}
@Override
public boolean isEntity() {
return false;
}
@Override
public boolean isIdProperty() {
return false;
}
@Override
public boolean isVersionProperty() {
return false;
}
@Override
public boolean isCollectionLike() {
return typeInformation.isCollectionLike();
}
@Override
public boolean isMap() {
return typeInformation.isMap();
}
@Override
public boolean isArray() {
return typeInformation.getType().isArray();
}
@Override
public boolean isTransient() {
return false;
}
@Override
public boolean isWritable() {
return false;
}
@Override
public boolean isImmutable() {
return false;
}
@Override
public boolean isAssociation() {
return false;
}
@Nullable
@Override
public Class<?> getComponentType() {
return Optional.ofNullable(typeInformation.getComponentType()).map(TypeInformation::getType).orElse(null);
}
@Override
public Class<?> getRawType() {
return typeInformation.getType();
}
@Nullable
@Override
public Class<?> getMapValueType() {
return Optional.ofNullable(typeInformation.getMapValueType()).map(TypeInformation::getType).orElse(null);
}
@Override
public Class<?> getActualType() {
return typeInformation.getRequiredActualType().getType();
}
@Nullable
@Override
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
return null;
}
@Nullable
@Override
public <A extends Annotation> A findPropertyOrOwnerAnnotation(Class<A> annotationType) {
return null;
}
@Override
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
return false;
}
@Override
public boolean usePropertyAccess() {
return false;
}
@Nullable
@Override
public Class<?> getAssociationTargetType() {
return null;
}
@Override
public void setColumnName(CqlIdentifier columnName) {
throw new UnsupportedOperationException();
}
@Override
@Deprecated
public void setForceQuote(boolean forceQuote) {
throw new UnsupportedOperationException();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
throw new UnsupportedOperationException();
}
}

View File

@@ -31,20 +31,12 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.BasicMapId;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.Embedded;
import org.springframework.data.cassandra.core.mapping.*;
import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty;
import org.springframework.data.cassandra.core.mapping.EmbeddedEntityOperations;
import org.springframework.data.cassandra.core.mapping.MapId;
import org.springframework.data.cassandra.core.mapping.MapIdentifiable;
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PreferredConstructor;
@@ -54,7 +46,6 @@ 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.ParameterValueProvider;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
@@ -284,11 +275,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return new ConvertingPropertyAccessor<>(propertyAccessor, getConversionService());
}
private <S> PersistentEntityParameterValueProvider<CassandraPersistentProperty> newParameterValueProvider(
ConversionContext context, CassandraPersistentEntity<S> entity, CassandraValueProvider valueProvider) {
private <S> CassandraPersistentEntityParameterValueProvider newParameterValueProvider(ConversionContext context,
CassandraPersistentEntity<S> entity, CassandraValueProvider valueProvider) {
return new PersistentEntityParameterValueProvider<>(entity,
new MappingAndConvertingValueProvider(valueProvider, context), null);
return new CassandraPersistentEntityParameterValueProvider(entity, valueProvider, context, null);
}
@SuppressWarnings("unchecked")
@@ -416,8 +406,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
if (persistenceConstructor != null && persistenceConstructor.hasParameters()) {
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(valueProvider.getSource(), spELContext);
PersistentEntityParameterValueProvider<CassandraPersistentProperty> parameterValueProvider = newParameterValueProvider(
context, entity, valueProvider);
ParameterValueProvider<CassandraPersistentProperty> parameterValueProvider = newParameterValueProvider(context,
entity, valueProvider);
provider = new ConverterAwareSpELExpressionParameterValueProvider(evaluator, getConversionService(),
parameterValueProvider, context);
} else {
@@ -1164,50 +1154,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
}
/**
* {@link CassandraRowValueProvider} that delegates reads to {@link CassandraValueProvider} applying mapping and
* custom conversion from {@link MappingCassandraConverter}.
*
* @author Mark Paluch
* @since 1.5.1
*/
class MappingAndConvertingValueProvider implements CassandraValueProvider {
private final CassandraValueProvider parent;
private final ConversionContext context;
public MappingAndConvertingValueProvider(CassandraValueProvider parent, ConversionContext context) {
this.parent = parent;
this.context = context;
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.convert.CassandraValueProvider#hasProperty(org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty)
*/
@Override
public boolean hasProperty(CassandraPersistentProperty property) {
return this.parent.hasProperty(property);
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.model.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/
@Nullable
@Override
@SuppressWarnings("unchecked")
public <T> T getPropertyValue(CassandraPersistentProperty property) {
return (T) getReadValue(context, this.parent, property);
}
/* (non-Javadoc)
* @see org.springframework.data.cassandra.core.convert.CassandraValueProvider#getSource()
*/
@Override
public Object getSource() {
return parent.getSource();
}
}
/**
* Conversion context holding references to simple {@link ValueConverter} and {@link ContainerValueConverter}.
* Entrypoint for recursive conversion of {@link Row} and other types.
@@ -1311,4 +1257,74 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
}
}
/**
* Cassandra-specific {@link ParameterValueProvider} considering {@link Column @Column} and {@link Element @Element}
* annotations on constructor parameters.
*
* @since 3.2
*/
class CassandraPersistentEntityParameterValueProvider implements ParameterValueProvider<CassandraPersistentProperty> {
private final CassandraPersistentEntity<?> entity;
private final CassandraValueProvider provider;
private final ConversionContext context;
private final @Nullable Object parent;
public CassandraPersistentEntityParameterValueProvider(CassandraPersistentEntity<?> entity,
CassandraValueProvider provider, ConversionContext context, @Nullable Object parent) {
this.entity = entity;
this.provider = provider;
this.context = context;
this.parent = parent;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T, CassandraPersistentProperty> parameter) {
PreferredConstructor<CassandraPersistentEntity<?>, CassandraPersistentProperty> constructor = (PreferredConstructor<CassandraPersistentEntity<?>, CassandraPersistentProperty>) entity
.getPersistenceConstructor();
if (constructor != null && constructor.isEnclosingClassParameter(parameter)) {
return (T) parent;
}
String name = parameter.getName();
if (name == null) {
throw new MappingException(String.format("Parameter %s does not have a name!", parameter));
}
CassandraPersistentProperty property = getPersistentProperty(name, parameter.getType(),
parameter.getAnnotations());
if (property == null) {
throw new MappingException(String.format("No property %s found on entity %s to bind constructor parameter to!",
name, entity.getType()));
}
return (T) getReadValue(context, provider, property);
}
@Nullable
private <T> CassandraPersistentProperty getPersistentProperty(String name, TypeInformation<?> typeInformation,
MergedAnnotations annotations) {
CassandraPersistentProperty property = entity.getPersistentProperty(name);
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
return new AnnotatedCassandraConstructorProperty(
property == null ? new CassandraConstructorProperty(name, entity, typeInformation) : property, annotations);
}
return property;
}
}
}

View File

@@ -41,10 +41,11 @@ import java.lang.annotation.Target;
*
* @author Alex Shvid
* @author Matthew T. Adams
* @author Mark Paluch
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface Column {
/**

View File

@@ -34,7 +34,7 @@ import java.lang.annotation.Target;
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD })
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
public @interface Element {
/**

View File

@@ -46,15 +46,8 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.BasicMapId;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.CassandraType;
import org.springframework.data.cassandra.core.mapping.Embedded;
import org.springframework.data.cassandra.core.mapping.MapId;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyClass;
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.core.mapping.Table;
import org.springframework.data.cassandra.core.mapping.*;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.domain.AllPossibleTypes;
import org.springframework.data.cassandra.domain.CompositeKey;
import org.springframework.data.cassandra.domain.TypeWithCompositeKey;
@@ -67,6 +60,8 @@ import org.springframework.data.cassandra.test.util.RowMockUtil;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.type.DataTypes;
import com.datastax.oss.driver.internal.core.data.DefaultTupleValue;
import com.datastax.oss.driver.internal.core.type.DefaultTupleType;
/**
* Unit tests for {@link MappingCassandraConverter}.
@@ -1014,6 +1009,39 @@ public class MappingCassandraConverterUnitTests {
assertThat(insert).containsEntry(CqlIdentifier.fromCql("conditionmap"), Collections.singletonMap(0, 1));
}
@Test
void shouldConsiderColumnAnnotationOnConstructor() {
rowMock = RowMockUtil.newRowMock(RowMockUtil.column("fn", "Walter", DataTypes.ASCII),
RowMockUtil.column("firstname", "Heisenberg", DataTypes.ASCII),
RowMockUtil.column("lastname", "White", DataTypes.ASCII));
WithColumnAnnotationInConstructor converted = this.mappingCassandraConverter
.read(WithColumnAnnotationInConstructor.class, rowMock);
assertThat(converted.firstname).isEqualTo("Walter");
assertThat(converted.lastname).isEqualTo("White");
}
@Test
void shouldConsiderElementAnnotationOnConstructor() {
DefaultTupleValue value = new DefaultTupleValue(
new DefaultTupleType(Arrays.asList(DataTypes.ASCII, DataTypes.ASCII, DataTypes.ASCII)));
value.setString(0, "Zero");
value.setString(1, "One");
value.setString(2, "Two");
rowMock = RowMockUtil.newRowMock(RowMockUtil.column("firstname", "Heisenberg", DataTypes.ASCII),
RowMockUtil.column("tuple", value, value.getType()));
WithMappedTuple converted = this.mappingCassandraConverter.read(WithMappedTuple.class, rowMock);
assertThat(converted.firstname).isEqualTo("Heisenberg");
assertThat(converted.tuple.firstname).isEqualTo("Two");
}
private static List<Object> getValues(Map<CqlIdentifier, Object> statement) {
return new ArrayList<>(statement.values());
}
@@ -1263,6 +1291,37 @@ public class MappingCassandraConverterUnitTests {
}
}
private static class WithColumnAnnotationInConstructor {
String firstname;
final @Transient String lastname;
public WithColumnAnnotationInConstructor(@Column("fn") String firstname, @Column("lastname") String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
}
private static class WithMappedTuple {
String firstname;
TupleWithElementAnnotationInConstructor tuple;
}
@Tuple
private static class TupleWithElementAnnotationInConstructor {
@Element(0) String zero;
@Element(1) String one;
@Element(2) String two;
@Transient String firstname;
public TupleWithElementAnnotationInConstructor(@Element(2) String firstname) {
this.firstname = firstname;
}
}
@ToString
static class WithNullableEmbeddedType {

View File

@@ -7,6 +7,7 @@ This chapter summarizes changes and new features for each release.
== What's new in Spring Data for Apache Cassandra 3.2
* <<cassandra.template.prepared-statements,Support for prepared statements>> using `CassandraTemplate` and repositories (enabled by default).
* `@Column` and `@Element` can be used on constructor arguments.
[[new-features.3-1-0]]
== What's new in Spring Data for Apache Cassandra 3.1

View File

@@ -432,6 +432,7 @@ In order to reference a property of a given `Row`/`UdtValue`/`TupleValue` one ha
Entity-bound insert and update statements do not include this property.
* `@Column`: Applied at the field level.
Describes the column name as it is represented in the Cassandra table, thus letting the name differ from the field name of the class.
Can be used on constructor arguments to customize the column name during constructor creation.
* `@Embedded`: Applied at the field level.
Enables embedded object usage for types mapped to a table or a user-defined type.
Properties of the embedded object are flattened into the structure of its parent.
@@ -448,6 +449,7 @@ Types are derived from the declaration by default.
* `@Tuple`: Applied at the type level to use a type as a mapped tuple.
* `@Element`: Applied at the field level to specify element or field ordinals within a mapped tuple.
Types are derived from the property declaration by default.
Can be used on constructor arguments to customize tuple element ordinals during constructor creation.
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations.
The initial value is `zero` which is bumped automatically on every update.