DATACASS-167 - Add support for embedded entities.
Embedded entities are used to design value objects in your Java domain model whose properties are flattened out into the table.
In the following example you see, that User.name is annotated with Embedded.
The consequence of this is that all properties of `UserName` are folded into the `user` table which consists of 3 columns (user_id, firstname, lastname).
Embedded entities may only contain simple property types. It is not possible to nest an embedded entity into another embedded one.
However, if the firstname and lastname column values are actually null within the result set, the entire property name will be set to null according to the onEmpty of Embedded, which nulls objects when all nested properties are null.
Opposite to this behaviour USE_EMPTY tries to create a new instance using either a default constructor or one that accepts nullable parameter values from the result set.
public class User {
@PrimaryKey("user_id")
private String userId;
@Embedded(onEmpty = USE_NULL)
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
}
Original pull request: #173.
This commit is contained in:
committed by
Mark Paluch
parent
b2031724ff
commit
a59d012fb9
@@ -42,6 +42,7 @@ import org.springframework.util.StringUtils;
|
||||
* index-annotated {@link CassandraPersistentProperty properties}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
* @see Indexed
|
||||
* @see SASI
|
||||
@@ -124,7 +125,7 @@ class IndexSpecificationFactory {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
private static CreateIndexSpecification createIndexSpecification(Indexed annotation,
|
||||
static CreateIndexSpecification createIndexSpecification(Indexed annotation,
|
||||
CassandraPersistentProperty property) {
|
||||
|
||||
CreateIndexSpecification index;
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.function.Function;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -41,6 +40,9 @@ 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.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;
|
||||
@@ -83,6 +85,7 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
|
||||
* @author Mark Paluch
|
||||
* @author Antoine Toulme
|
||||
* @author John Blum
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
implements ApplicationContextAware, BeanClassLoaderAware {
|
||||
@@ -100,6 +103,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
private SpELContext spELContext;
|
||||
|
||||
private final DefaultColumnTypeResolver cassandraTypeResolver;
|
||||
private final EmbeddedEntityOperations embeddedEntityOperations;
|
||||
|
||||
/**
|
||||
* Create a new {@link MappingCassandraConverter} with a {@link CassandraMappingContext}.
|
||||
@@ -117,6 +121,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
userTypeName -> getUserTypeResolver().resolveType(userTypeName), this::getCodecRegistry,
|
||||
this::getCustomConversions);
|
||||
this.setCustomConversions(conversions);
|
||||
this.embeddedEntityOperations = new EmbeddedEntityOperations(mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +142,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
userTypeName -> getUserTypeResolver().resolveType(userTypeName), this::getCodecRegistry,
|
||||
this::getCustomConversions);
|
||||
this.setCustomConversions(mappingContext.getCustomConversions());
|
||||
this.embeddedEntityOperations = new EmbeddedEntityOperations(mappingContext);
|
||||
}
|
||||
|
||||
private static ConversionService newConversionService() {
|
||||
@@ -404,7 +410,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.isCompositePrimaryKey() || valueProvider.hasProperty(property)) {
|
||||
if (property.isCompositePrimaryKey() || valueProvider.hasProperty(property) || property.isEmbedded()) {
|
||||
propertyAccessor.setProperty(property, getReadValue(valueProvider, property));
|
||||
}
|
||||
}
|
||||
@@ -493,11 +499,21 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Adding map.entry [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
if (value != null && property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
sink.put(property.getRequiredColumnName(), value);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Mapping embedded property [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
|
||||
write(value, sink, embeddedEntityOperations.getEntity(property));
|
||||
} else {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Adding map.entry [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
|
||||
sink.put(property.getRequiredColumnName(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,6 +641,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
continue;
|
||||
}
|
||||
|
||||
// value resolution
|
||||
Object value = getWriteValue(property, propertyAccessor);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
@@ -636,6 +653,23 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
log.debug("Adding udt.value [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Mapping embedded property [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CassandraPersistentEntity<?> targetEntity = embeddedEntityOperations.getEntity(property);
|
||||
writeUDTValue(new ConvertingPropertyAccessor<>(targetEntity.getPropertyAccessor(value), getConversionService()),
|
||||
udtValue, targetEntity);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
TypeCodec<Object> typeCodec = getCodec(property);
|
||||
|
||||
udtValue.set(property.getRequiredColumnName().toString(), value, typeCodec);
|
||||
@@ -900,6 +934,12 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return doReadEntity(keyEntity, valueProvider);
|
||||
}
|
||||
|
||||
if (property.isEntity() && property.isEmbedded()) {
|
||||
|
||||
CassandraPersistentEntity<?> targetEntity = embeddedEntityOperations.getEntity(property);
|
||||
return isNullEmbedded(targetEntity, property, valueProvider) ? null : doReadEntity(targetEntity, valueProvider);
|
||||
}
|
||||
|
||||
if (!valueProvider.hasProperty(property)) {
|
||||
return null;
|
||||
}
|
||||
@@ -908,6 +948,31 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return value == null ? null : convertReadValue(value, property.getTypeInformation());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity the property domain type
|
||||
* @param property the current property annotated with {@link Embedded}.
|
||||
* @param valueProvider
|
||||
* @return {@literal true} if the property represents a {@link Embedded.Nullable nullable embedded} entity where all
|
||||
* values obtainable from the given {@link CassandraValueProvider} are {@literal null}.
|
||||
* @since 3.0
|
||||
*/
|
||||
private boolean isNullEmbedded(CassandraPersistentEntity<?> entity, CassandraPersistentProperty property,
|
||||
CassandraValueProvider valueProvider) {
|
||||
|
||||
if (OnEmpty.USE_EMPTY.equals(property.findAnnotation(Embedded.class).onEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CassandraPersistentProperty embeddedProperty : entity) {
|
||||
|
||||
if (valueProvider.hasProperty(embeddedProperty) && valueProvider.getPropertyValue(embeddedProperty) != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object convertReadValue(Object value, TypeInformation<?> typeInformation) {
|
||||
@@ -1123,5 +1188,4 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return parent.getSource();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.stream.Collectors;
|
||||
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.EmbeddedEntityOperations;
|
||||
import org.springframework.data.cassandra.core.query.ColumnName;
|
||||
import org.springframework.data.cassandra.core.query.Columns;
|
||||
import org.springframework.data.cassandra.core.query.Columns.ColumnSelector;
|
||||
@@ -53,6 +54,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
* Map {@link org.springframework.data.cassandra.core.query.Query} to CQL-specific data types.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @see ColumnName
|
||||
* @see Columns
|
||||
* @see Criteria
|
||||
@@ -517,10 +519,29 @@ public class QueryMapper {
|
||||
@Override
|
||||
public ColumnName getMappedKey() {
|
||||
|
||||
return path.map(PersistentPropertyPath::getLeafProperty) //
|
||||
.map(CassandraPersistentProperty::getColumnName) //
|
||||
.map(ColumnName::from) //
|
||||
.orElse(name);
|
||||
if (!path.isPresent()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
boolean embedded = false;
|
||||
CassandraPersistentEntity<?> parentEntity = null;
|
||||
CassandraPersistentProperty leafProperty = null;
|
||||
for (CassandraPersistentProperty p : path.get()) {
|
||||
|
||||
leafProperty = p;
|
||||
if (embedded) {
|
||||
|
||||
embedded = false;
|
||||
leafProperty = parentEntity.getPersistentProperty(p.getName());
|
||||
parentEntity = null;
|
||||
}
|
||||
if (p.isEmbedded() && p.isEntity()) {
|
||||
embedded = true;
|
||||
parentEntity = new EmbeddedEntityOperations(mappingContext).getEntity(p);
|
||||
}
|
||||
}
|
||||
|
||||
return ColumnName.from(leafProperty.getColumnName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecifica
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.EmbeddedEntityOperations;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -43,6 +45,7 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry;
|
||||
* Factory for Cassandra Schema objects such as user-defined types, tables and indexes.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see CreateUserTypeSpecification
|
||||
* @see CreateTableSpecification
|
||||
@@ -55,6 +58,8 @@ public class SchemaFactory {
|
||||
|
||||
private final ColumnTypeResolver typeResolver;
|
||||
|
||||
private final EmbeddedEntityOperations embeddedEntityOperations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SchemaFactory} given {@link CassandraConverter}.
|
||||
*
|
||||
@@ -67,6 +72,7 @@ public class SchemaFactory {
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
this.typeResolver = new DefaultColumnTypeResolver(mappingContext, ShallowUserTypeResolver.INSTANCE,
|
||||
converter::getCodecRegistry, converter::getCustomConversions);
|
||||
this.embeddedEntityOperations = new EmbeddedEntityOperations(this.mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,6 +94,7 @@ public class SchemaFactory {
|
||||
this.mappingContext = mappingContext;
|
||||
this.typeResolver = new DefaultColumnTypeResolver(mappingContext, ShallowUserTypeResolver.INSTANCE,
|
||||
() -> codecRegistry, () -> customConversions);
|
||||
this.embeddedEntityOperations = new EmbeddedEntityOperations(this.mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +158,16 @@ public class SchemaFactory {
|
||||
primaryKeyProperty.getPrimaryKeyOrdering());
|
||||
}
|
||||
}
|
||||
} else if (property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
CassandraPersistentEntity<?> embeddedEntity = embeddedEntityOperations.getEntity(property);
|
||||
|
||||
for (CassandraPersistentProperty embeddedProperty : embeddedEntity) {
|
||||
|
||||
DataType dataType = getDataType(embeddedProperty);
|
||||
specification.column(embeddedProperty.getRequiredColumnName(), dataType);
|
||||
}
|
||||
|
||||
} else {
|
||||
DataType type = UserTypeUtil.potentiallyFreeze(getDataType(property));
|
||||
|
||||
@@ -230,6 +247,17 @@ public class SchemaFactory {
|
||||
for (CassandraPersistentProperty property : entity) {
|
||||
if (property.isCompositePrimaryKey()) {
|
||||
indexes.addAll(getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(property)));
|
||||
}
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
if (property.isAnnotationPresent(Indexed.class)) {
|
||||
Indexed indexed = property.findAnnotation(Indexed.class);
|
||||
for (CassandraPersistentProperty embeddedProperty : embeddedEntityOperations.getEntity(property)) {
|
||||
indexes.add(IndexSpecificationFactory.createIndexSpecification(indexed, embeddedProperty));
|
||||
}
|
||||
} else {
|
||||
indexes.addAll(getCreateIndexSpecificationsFor(embeddedEntityOperations.getEntity(property)));
|
||||
}
|
||||
} else {
|
||||
indexes.addAll(IndexSpecificationFactory.createIndexSpecifications(property));
|
||||
}
|
||||
@@ -252,8 +280,19 @@ public class SchemaFactory {
|
||||
CreateUserTypeSpecification specification = CreateUserTypeSpecification.createType(entity.getTableName());
|
||||
|
||||
for (CassandraPersistentProperty property : entity) {
|
||||
// Use frozen literal to not resolve types from Cassandra; At this stage, they might be not created yet.
|
||||
specification.field(property.getRequiredColumnName(), UserTypeUtil.potentiallyFreeze(getDataType(property)));
|
||||
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
|
||||
CassandraPersistentEntity<?> embeddedEntity = embeddedEntityOperations.getEntity(property);
|
||||
for (CassandraPersistentProperty embeddedProperty : embeddedEntity) {
|
||||
|
||||
DataType dataType = getDataType(embeddedProperty);
|
||||
specification.field(embeddedProperty.getRequiredColumnName(), dataType);
|
||||
}
|
||||
} else {
|
||||
// Use frozen literal to not resolve types from Cassandra; At this stage, they might be not created yet.
|
||||
specification.field(property.getRequiredColumnName(), UserTypeUtil.potentiallyFreeze(getDataType(property)));
|
||||
}
|
||||
}
|
||||
|
||||
if (specification.getFields().isEmpty()) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
* @author David T. Webb
|
||||
* @author Mark Paluch
|
||||
* @author John Blum
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public interface CassandraPersistentProperty
|
||||
extends PersistentProperty<CassandraPersistentProperty>, ApplicationContextAware {
|
||||
@@ -157,6 +158,14 @@ public interface CassandraPersistentProperty
|
||||
*/
|
||||
boolean isPrimaryKeyColumn();
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the property should be embedded.
|
||||
* @since 3.0
|
||||
*/
|
||||
default boolean isEmbedded() {
|
||||
return findAnnotation(Embedded.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an {@link AnnotatedType} by {@code annotationType} derived from the property type. Annotated type is looked up
|
||||
* by introspecting property field/accessors. Collection/Map-like types are introspected for type annotations within
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.mapping;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import javax.annotation.meta.When;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
public @interface Embedded {
|
||||
/**
|
||||
* Set the load strategy for the embedded object if all contained fields yield {@literal null} values.
|
||||
* <p />
|
||||
* {@link Nullable @Embedded.Nullable} and {@link Empty @Embedded.Empty} offer shortcuts for this.
|
||||
*
|
||||
* @return never {@link} null.
|
||||
*/
|
||||
OnEmpty onEmpty();
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* Load strategy to be used {@link Embedded#onEmpty()}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.1
|
||||
*/
|
||||
enum OnEmpty {
|
||||
USE_NULL, USE_EMPTY
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for a nullable embedded property.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* @Embedded.Nullable
|
||||
* private Address address;
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* @Embedded(onEmpty = USE_NULL)
|
||||
* @javax.annotation.Nonnull(when = When.MAYBE)
|
||||
* private Address address;
|
||||
*
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_NULL)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@javax.annotation.Nonnull(when = When.MAYBE)
|
||||
@interface Nullable {
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for an empty embedded property.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* @Embedded.Empty
|
||||
* private Address address;
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* @Embedded(onEmpty = USE_EMPTY)
|
||||
* @javax.annotation.Nonnull(when = When.NEVER)
|
||||
* private Address address;
|
||||
*
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_EMPTY)
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@javax.annotation.Nonnull(when = When.NEVER)
|
||||
@interface Empty {
|
||||
|
||||
/**
|
||||
* @return prefix for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.mapping;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.Embedded.Nullable;
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
public class EmbeddedEntityOperations {
|
||||
|
||||
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
|
||||
|
||||
public EmbeddedEntityOperations(MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
public CassandraPersistentEntity<?> getEntity(CassandraPersistentProperty property) {
|
||||
return withPrefix(getPrefix(property), mappingContext.getPersistentEntity(property));
|
||||
}
|
||||
|
||||
static <T> CassandraPersistentEntity<T> withPrefix(@org.springframework.lang.Nullable String prefix,
|
||||
CassandraPersistentEntity<T> source) {
|
||||
|
||||
if (!StringUtils.hasText(prefix)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
return new PrefixedCassandraPersistentEntity<>(prefix, source);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String getPrefix(CassandraPersistentProperty property) {
|
||||
|
||||
Embedded embedded = property.findAnnotation(Embedded.class);
|
||||
return embedded != null ? embedded.prefix() : null;
|
||||
}
|
||||
|
||||
static class PrefixedCassandraPersistentEntity<T> implements CassandraPersistentEntity<T> {
|
||||
|
||||
private final String prefix;
|
||||
private CassandraPersistentEntity<T> delegate;
|
||||
|
||||
public PrefixedCassandraPersistentEntity(String prefix, CassandraPersistentEntity<T> delegate) {
|
||||
|
||||
this.prefix = prefix;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompositePrimaryKey() {
|
||||
return delegate.isCompositePrimaryKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setForceQuote(boolean forceQuote) {
|
||||
delegate.setForceQuote(forceQuote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CqlIdentifier getTableName() {
|
||||
return delegate.getTableName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setTableName(org.springframework.data.cassandra.core.cql.CqlIdentifier tableName) {
|
||||
delegate.setTableName(tableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTableName(CqlIdentifier tableName) {
|
||||
delegate.setTableName(tableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTupleType() {
|
||||
return delegate.isTupleType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserDefinedType() {
|
||||
return delegate.isUserDefinedType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public PreferredConstructor<T, CassandraPersistentProperty> getPersistenceConstructor() {
|
||||
return delegate.getPersistenceConstructor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructorArgument(PersistentProperty<?> property) {
|
||||
return delegate.isConstructorArgument(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty(PersistentProperty<?> property) {
|
||||
return delegate.isIdProperty(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionProperty(PersistentProperty<?> property) {
|
||||
return delegate.isVersionProperty(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getIdProperty() {
|
||||
return delegate.getIdProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CassandraPersistentProperty getRequiredIdProperty() {
|
||||
return delegate.getRequiredIdProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getVersionProperty() {
|
||||
return delegate.getVersionProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CassandraPersistentProperty getRequiredVersionProperty() {
|
||||
return delegate.getRequiredVersionProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getPersistentProperty(String name) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, delegate.getPersistentProperty(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CassandraPersistentProperty getRequiredPersistentProperty(String name) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, delegate.getRequiredPersistentProperty(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, delegate.getPersistentProperty(annotationType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<CassandraPersistentProperty> getPersistentProperties(Class<? extends Annotation> annotationType) {
|
||||
return delegate.getPersistentProperties(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasIdProperty() {
|
||||
return delegate.hasIdProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVersionProperty() {
|
||||
return delegate.hasVersionProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getType() {
|
||||
return delegate.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias() {
|
||||
return delegate.getTypeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<T> getTypeInformation() {
|
||||
return delegate.getTypeInformation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithProperties(PropertyHandler<CassandraPersistentProperty> handler) {
|
||||
|
||||
delegate.doWithProperties((PropertyHandler<CassandraPersistentProperty>) property -> {
|
||||
handler.doWithPersistentProperty(wrap(property));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithProperties(SimplePropertyHandler handler) {
|
||||
|
||||
delegate.doWithProperties((SimplePropertyHandler) property -> {
|
||||
|
||||
if (property instanceof CassandraPersistentProperty) {
|
||||
handler.doWithPersistentProperty(wrap((CassandraPersistentProperty) property));
|
||||
} else {
|
||||
handler.doWithPersistentProperty(property);
|
||||
}
|
||||
});
|
||||
delegate.doWithProperties(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithAssociations(AssociationHandler<CassandraPersistentProperty> handler) {
|
||||
delegate.doWithAssociations(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithAssociations(SimpleAssociationHandler handler) {
|
||||
delegate.doWithAssociations(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return delegate.findAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
|
||||
return delegate.getRequiredAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> boolean isAnnotationPresent(Class<A> annotationType) {
|
||||
return delegate.isAnnotationPresent(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
|
||||
return delegate.getPropertyAccessor(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
|
||||
return delegate.getPropertyPathAccessor(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentifierAccessor getIdentifierAccessor(Object bean) {
|
||||
return delegate.getIdentifierAccessor(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew(Object bean) {
|
||||
return delegate.isNew(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImmutable() {
|
||||
return delegate.isImmutable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresPropertyPopulation() {
|
||||
return delegate.requiresPropertyPopulation();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<CassandraPersistentProperty> iterator() {
|
||||
|
||||
List<CassandraPersistentProperty> target = new ArrayList<>();
|
||||
delegate.iterator().forEachRemaining(it -> target.add(wrap(it)));
|
||||
return target.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super CassandraPersistentProperty> action) {
|
||||
delegate.forEach(it -> action.accept(wrap(it)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<CassandraPersistentProperty> spliterator() {
|
||||
return delegate.spliterator();
|
||||
}
|
||||
|
||||
private PrefixedCassandraPersistentProperty wrap(CassandraPersistentProperty source) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, source);
|
||||
}
|
||||
}
|
||||
|
||||
static class PrefixedCassandraPersistentProperty implements CassandraPersistentProperty {
|
||||
|
||||
private final String prefix;
|
||||
private final CassandraPersistentProperty delegate;
|
||||
|
||||
public PrefixedCassandraPersistentProperty(String prefix, CassandraPersistentProperty delegate) {
|
||||
this.prefix = prefix;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setColumnName(org.springframework.data.cassandra.core.cql.CqlIdentifier columnName) {
|
||||
delegate.setColumnName(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColumnName(CqlIdentifier columnName) {
|
||||
delegate.setColumnName(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CqlIdentifier getColumnName() {
|
||||
return CqlIdentifier.fromInternal(prefix + delegate.getColumnName().asInternal());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setForceQuote(boolean forceQuote) {
|
||||
delegate.setForceQuote(forceQuote);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Integer getOrdinal() {
|
||||
return delegate.getOrdinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredOrdinal() {
|
||||
return delegate.getRequiredOrdinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.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
|
||||
public boolean isEmbedded() {
|
||||
return delegate.isEmbedded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.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
|
||||
@org.springframework.lang.Nullable
|
||||
public Method getGetter() {
|
||||
return delegate.getGetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getRequiredGetter() {
|
||||
return delegate.getRequiredGetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Method getSetter() {
|
||||
return delegate.getSetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getRequiredSetter() {
|
||||
return delegate.getRequiredSetter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Method getWither() {
|
||||
return delegate.getWither();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getRequiredWither() {
|
||||
return delegate.getRequiredWither();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Field getField() {
|
||||
return delegate.getField();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field getRequiredField() {
|
||||
return delegate.getRequiredField();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public String getSpelExpression() {
|
||||
return delegate.getSpelExpression();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Association<CassandraPersistentProperty> getAssociation() {
|
||||
return delegate.getAssociation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Association<CassandraPersistentProperty> getRequiredAssociation() {
|
||||
return delegate.getRequiredAssociation();
|
||||
}
|
||||
|
||||
@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
|
||||
@org.springframework.lang.Nullable
|
||||
public Class<?> getComponentType() {
|
||||
return delegate.getComponentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getRawType() {
|
||||
return delegate.getRawType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Class<?> getMapValueType() {
|
||||
return delegate.getMapValueType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getActualType() {
|
||||
return delegate.getActualType();
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
return delegate.findAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType) throws IllegalStateException {
|
||||
return delegate.getRequiredAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.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
|
||||
public boolean hasActualTypeAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return delegate.hasActualTypeAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public Class<?> getAssociationTargetType() {
|
||||
return delegate.getAssociationTargetType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PersistentPropertyAccessor<T> getAccessorForOwner(T owner) {
|
||||
return delegate.getAccessorForOwner(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
delegate.setApplicationContext(applicationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,15 +37,17 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
|
||||
import org.springframework.data.cassandra.core.cql.CqlTemplate;
|
||||
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
|
||||
import org.springframework.data.cassandra.core.mapping.BasicMapId;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
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.SimpleUserTypeResolver;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
|
||||
import org.springframework.data.cassandra.core.query.Columns;
|
||||
import org.springframework.data.cassandra.core.query.Query;
|
||||
@@ -60,12 +62,14 @@ import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.util.Version;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CassandraTemplate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingIntegrationTest {
|
||||
|
||||
@@ -79,6 +83,7 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
public void setUp() {
|
||||
|
||||
MappingCassandraConverter converter = new MappingCassandraConverter();
|
||||
converter.setUserTypeResolver(new SimpleUserTypeResolver(session, CqlIdentifier.fromCql(keyspace)));
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
cassandraVersion = CassandraVersion.get(session);
|
||||
@@ -90,10 +95,20 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
SchemaTestUtils.potentiallyCreateTableFor(BookReference.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TimeClass.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(TypeWithCompositeKey.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(WithNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(WithEmptyEmbeddedType.class, template);
|
||||
SchemaTestUtils.potentiallyCreateTableFor(WithPrefixedNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.createTableAndTypes(OuterWithNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.createTableAndTypes(OuterWithPrefixedNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.truncate(User.class, template);
|
||||
SchemaTestUtils.truncate(UserToken.class, template);
|
||||
SchemaTestUtils.truncate(BookReference.class, template);
|
||||
SchemaTestUtils.truncate(TypeWithCompositeKey.class, template);
|
||||
SchemaTestUtils.truncate(WithNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.truncate(WithEmptyEmbeddedType.class, template);
|
||||
SchemaTestUtils.truncate(WithPrefixedNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.truncate(OuterWithNullableEmbeddedType.class, template);
|
||||
SchemaTestUtils.truncate(OuterWithPrefixedNullableEmbeddedType.class, template);
|
||||
}
|
||||
|
||||
@Test // DATACASS-343
|
||||
@@ -556,6 +571,119 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
assertThat(iterations).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadPrefixedEmbeddedCorrectly() {
|
||||
|
||||
WithPrefixedNullableEmbeddedType entity = new WithPrefixedNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.nested.firstname = "fn";
|
||||
entity.nested.age = 30;
|
||||
|
||||
template.insert(WithPrefixedNullableEmbeddedType.class).one(entity);
|
||||
|
||||
WithPrefixedNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
WithPrefixedNullableEmbeddedType.class);
|
||||
assertThat(target).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadEmbeddedCorrectly() {
|
||||
|
||||
WithNullableEmbeddedType entity = new WithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.nested.firstname = "fn";
|
||||
entity.nested.age = 30;
|
||||
|
||||
template.insert(WithNullableEmbeddedType.class).one(entity);
|
||||
|
||||
WithNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
WithNullableEmbeddedType.class);
|
||||
assertThat(target).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadNullableEmbeddedCorrectly() {
|
||||
|
||||
WithNullableEmbeddedType entity = new WithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = null;
|
||||
|
||||
template.insert(WithNullableEmbeddedType.class).one(entity);
|
||||
|
||||
WithNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
WithNullableEmbeddedType.class);
|
||||
assertThat(target.id).isEqualTo("id-1");
|
||||
assertThat(target.nested).isNull();
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadEmptyEmbeddedCorrectly() {
|
||||
|
||||
WithEmptyEmbeddedType entity = new WithEmptyEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = null;
|
||||
|
||||
template.insert(WithEmptyEmbeddedType.class).one(entity);
|
||||
|
||||
WithEmptyEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")), WithEmptyEmbeddedType.class);
|
||||
assertThat(target.id).isEqualTo("id-1");
|
||||
assertThat(target.nested).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadEmbeddedUDTCorrectly() {
|
||||
|
||||
OuterWithNullableEmbeddedType entity = new OuterWithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new UDTWithNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-1";
|
||||
entity.udtValue.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.udtValue.nested.firstname = "fn";
|
||||
entity.udtValue.nested.age = 30;
|
||||
|
||||
template.insert(OuterWithNullableEmbeddedType.class).one(entity);
|
||||
|
||||
OuterWithNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
OuterWithNullableEmbeddedType.class);
|
||||
assertThat(target).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadPrefixedUdtEmbeddedCorrectly() {
|
||||
|
||||
OuterWithPrefixedNullableEmbeddedType entity = new OuterWithPrefixedNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new UDTWithPrefixedNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-1";
|
||||
entity.udtValue.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.udtValue.nested.firstname = "fn";
|
||||
entity.udtValue.nested.age = 30;
|
||||
|
||||
template.insert(OuterWithPrefixedNullableEmbeddedType.class).one(entity);
|
||||
|
||||
OuterWithPrefixedNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
OuterWithPrefixedNullableEmbeddedType.class);
|
||||
assertThat(target).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldSaveAndReadNullEmbeddedUDTCorrectly() {
|
||||
|
||||
OuterWithNullableEmbeddedType entity = new OuterWithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new UDTWithNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-1";
|
||||
entity.udtValue.nested = null;
|
||||
|
||||
template.insert(OuterWithNullableEmbeddedType.class).one(entity);
|
||||
|
||||
OuterWithNullableEmbeddedType target = template.selectOne(Query.query(where("id").is("id-1")),
|
||||
OuterWithNullableEmbeddedType.class);
|
||||
assertThat(target).isEqualTo(entity);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class TimeClass {
|
||||
|
||||
@@ -579,4 +707,69 @@ public class CassandraTemplateIntegrationTests extends AbstractKeyspaceCreatingI
|
||||
@PrimaryKeyColumn(type = PrimaryKeyType.PARTITIONED) String lastname;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithPrefixedNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithEmptyEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Empty EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
String firstname;
|
||||
Integer age;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class OuterWithNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
UDTWithNullableEmbeddedType udtValue;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class OuterWithPrefixedNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
UDTWithPrefixedNullableEmbeddedType udtValue;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class UDTWithNullableEmbeddedType {
|
||||
|
||||
String value;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class UDTWithPrefixedNullableEmbeddedType {
|
||||
|
||||
String value;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
|
||||
@@ -51,6 +52,7 @@ import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
* Unit tests for UDT through {@link MappingCassandraConverter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.Silent.class) // there are some unused stubbings in RowMockUtil but they're used in other
|
||||
public class MappingCassandraConverterUDTUnitTests {
|
||||
@@ -61,6 +63,12 @@ public class MappingCassandraConverterUDTUnitTests {
|
||||
.withField("name", DataTypes.TEXT).withField("displayname", DataTypes.TEXT).build();
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType currency = UserDefinedTypeBuilder.forName("mycurrency")
|
||||
.withField("currency", DataTypes.TEXT).build();
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType withnullableembeddedtype = UserDefinedTypeBuilder
|
||||
.forName("withnullableembeddedtype").withField("value", DataTypes.TEXT).withField("firstname", DataTypes.TEXT)
|
||||
.withField("age", DataTypes.INT).build();
|
||||
com.datastax.oss.driver.api.core.type.UserDefinedType withprefixednullableembeddedtype = UserDefinedTypeBuilder
|
||||
.forName("withnullableembeddedtype").withField("value", DataTypes.TEXT)
|
||||
.withField("prefixfirstname", DataTypes.TEXT).withField("prefixage", DataTypes.INT).build();
|
||||
|
||||
Row rowMock;
|
||||
|
||||
@@ -78,6 +86,10 @@ public class MappingCassandraConverterUDTUnitTests {
|
||||
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("manufacturer"))).thenReturn(manufacturer);
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("currency"))).thenReturn(currency);
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("withnullableembeddedtype")))
|
||||
.thenReturn(withnullableembeddedtype);
|
||||
when(userTypeResolver.resolveType(CqlIdentifier.fromCql("withprefixednullableembeddedtype")))
|
||||
.thenReturn(withprefixednullableembeddedtype);
|
||||
}
|
||||
|
||||
@Test // DATACASS-487, DATACASS-623
|
||||
@@ -127,6 +139,99 @@ public class MappingCassandraConverterUDTUnitTests {
|
||||
.contains("{currency:'EUR'}", "{currency:'USD'}");
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writeFlattensEmbeddedType() {
|
||||
|
||||
OuterWithNullableEmbeddedType entity = new OuterWithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new WithNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-string";
|
||||
entity.udtValue.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.udtValue.nested.firstname = "fn";
|
||||
entity.udtValue.nested.age = 30;
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink).containsEntry(CqlIdentifier.fromInternal("id"), "id-1");
|
||||
assertThat((UdtValue) sink.get(CqlIdentifier.fromInternal("udtvalue"))).extracting(UdtValue::getFormattedContents)
|
||||
.isEqualTo("{value:'value-string',firstname:'fn',age:30}");
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writeNullEmbeddedType() {
|
||||
|
||||
OuterWithNullableEmbeddedType entity = new OuterWithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new WithNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-string";
|
||||
entity.udtValue.nested = null;
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink).containsEntry(CqlIdentifier.fromInternal("id"), "id-1");
|
||||
assertThat((UdtValue) sink.get(CqlIdentifier.fromInternal("udtvalue"))).extracting(UdtValue::getFormattedContents)
|
||||
.isEqualTo("{value:'value-string',firstname:NULL,age:NULL}");
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writePrefixesEmbeddedType() {
|
||||
|
||||
OuterWithPrefixedNullableEmbeddedType entity = new OuterWithPrefixedNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.udtValue = new WithPrefixedNullableEmbeddedType();
|
||||
entity.udtValue.value = "value-string";
|
||||
entity.udtValue.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.udtValue.nested.firstname = "fn";
|
||||
entity.udtValue.nested.age = 30;
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink).containsEntry(CqlIdentifier.fromInternal("id"), "id-1");
|
||||
assertThat((UdtValue) sink.get(CqlIdentifier.fromInternal("udtvalue"))).extracting(UdtValue::getFormattedContents)
|
||||
.isEqualTo("{value:'value-string',prefixfirstname:'fn',prefixage:30}");
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void readEmbeddedType() {
|
||||
|
||||
UdtValue udtValue = withnullableembeddedtype.newValue().setString("value", "value-string")
|
||||
.setString("firstname", "fn").setInt("age", 30);
|
||||
|
||||
rowMock = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT),
|
||||
column("udtvalue", udtValue, withnullableembeddedtype));
|
||||
|
||||
OuterWithNullableEmbeddedType target = mappingCassandraConverter.read(OuterWithNullableEmbeddedType.class, rowMock);
|
||||
assertThat(target.getId()).isEqualTo("id-1");
|
||||
assertThat(target.udtValue).isNotNull();
|
||||
assertThat(target.udtValue.value).isEqualTo("value-string");
|
||||
assertThat(target.udtValue.nested.firstname).isEqualTo("fn");
|
||||
assertThat(target.udtValue.nested.age).isEqualTo(30);
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void readPrefixedEmbeddedType() {
|
||||
|
||||
UdtValue udtValue = withprefixednullableembeddedtype.newValue().setString("value", "value-string")
|
||||
.setString("prefixfirstname", "fn").setInt("prefixage", 30);
|
||||
|
||||
rowMock = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT),
|
||||
column("udtvalue", udtValue, withprefixednullableembeddedtype));
|
||||
|
||||
OuterWithPrefixedNullableEmbeddedType target = mappingCassandraConverter
|
||||
.read(OuterWithPrefixedNullableEmbeddedType.class, rowMock);
|
||||
assertThat(target.getId()).isEqualTo("id-1");
|
||||
assertThat(target.udtValue).isNotNull();
|
||||
assertThat(target.udtValue.value).isEqualTo("value-string");
|
||||
assertThat(target.udtValue.nested.firstname).isEqualTo("fn");
|
||||
assertThat(target.udtValue.nested.age).isEqualTo(30);
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@@ -148,4 +253,63 @@ public class MappingCassandraConverterUDTUnitTests {
|
||||
private static class Supplier {
|
||||
Map<Manufacturer, List<Currency>> acceptedCurrencies;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class OuterWithNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
WithNullableEmbeddedType udtValue;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class OuterWithPrefixedNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
WithPrefixedNullableEmbeddedType udtValue;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
String value;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class WithPrefixedNullableEmbeddedType {
|
||||
|
||||
String value;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@UserDefinedType
|
||||
@Data
|
||||
static class WithEmptyEmbeddedType {
|
||||
|
||||
String value;
|
||||
|
||||
@Embedded.Empty EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
String firstname;
|
||||
Integer age;
|
||||
|
||||
public String getFirstname() {
|
||||
return firstname;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ import static org.springframework.data.cassandra.core.mapping.BasicMapId.*;
|
||||
import static org.springframework.data.cassandra.test.util.RowMockUtil.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
@@ -37,7 +40,6 @@ import java.util.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
@@ -46,6 +48,7 @@ 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;
|
||||
@@ -68,6 +71,7 @@ import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
* Unit tests for {@link MappingCassandraConverter}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @soundtrack Outlandich - Dont Leave Me Feat Cyt (Sun Kidz Electrocore Mix)
|
||||
*/
|
||||
public class MappingCassandraConverterUnitTests {
|
||||
@@ -292,8 +296,7 @@ public class MappingCassandraConverterUnitTests {
|
||||
@Test // DATACASS-280
|
||||
public void shouldReadInetAddressCorrectly() throws UnknownHostException {
|
||||
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
|
||||
InetAddress localHost = InetAddress.getLoopbackAddress();
|
||||
rowMock = RowMockUtil.newRowMock(column("foo", localHost, DataTypes.UUID));
|
||||
|
||||
InetAddress result = mappingCassandraConverter.readRow(InetAddress.class, rowMock);
|
||||
@@ -1255,4 +1258,128 @@ public class MappingCassandraConverterUnitTests {
|
||||
this.firstname = firstname;
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@ToString
|
||||
static class WithPrefixedNullableEmbeddedType {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@ToString
|
||||
static class WithEmptyEmbeddedType {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Empty EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
String firstname;
|
||||
Integer age;
|
||||
@Transient String displayName;
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writeFlattensEmbeddedType() {
|
||||
|
||||
WithNullableEmbeddedType entity = new WithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.nested.firstname = "fn";
|
||||
entity.nested.age = 30;
|
||||
entity.nested.displayName = "dp-name";
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink) //
|
||||
.containsEntry(CqlIdentifier.fromCql("id"), "id-1") //
|
||||
.containsEntry(CqlIdentifier.fromCql("age"), 30) //
|
||||
.containsEntry(CqlIdentifier.fromCql("firstname"), "fn") //
|
||||
.doesNotContainKey(CqlIdentifier.fromCql("displayName"));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writePrefixesEmbeddedType() {
|
||||
|
||||
WithPrefixedNullableEmbeddedType entity = new WithPrefixedNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = new EmbeddedWithSimpleTypes();
|
||||
entity.nested.firstname = "fn";
|
||||
entity.nested.age = 30;
|
||||
entity.nested.displayName = "dp-name";
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink) //
|
||||
.containsEntry(CqlIdentifier.fromCql("id"), "id-1") //
|
||||
.containsEntry(CqlIdentifier.fromCql("prefixage"), 30) //
|
||||
.containsEntry(CqlIdentifier.fromCql("prefixfirstname"), "fn") //
|
||||
.doesNotContainKey(CqlIdentifier.fromCql("displayName"));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void writeNullEmbeddedType() {
|
||||
|
||||
WithNullableEmbeddedType entity = new WithNullableEmbeddedType();
|
||||
entity.id = "id-1";
|
||||
entity.nested = null;
|
||||
|
||||
Map<CqlIdentifier, Object> sink = new LinkedHashMap<>();
|
||||
|
||||
mappingCassandraConverter.write(entity, sink);
|
||||
|
||||
assertThat(sink) //
|
||||
.containsEntry(CqlIdentifier.fromCql("id"), "id-1") //
|
||||
.doesNotContainKey(CqlIdentifier.fromCql("age")) //
|
||||
.doesNotContainKey(CqlIdentifier.fromCql("firstname")) //
|
||||
.doesNotContainKey(CqlIdentifier.fromCql("displayName"));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void readEmbeddedType() {
|
||||
|
||||
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("age", 30, DataTypes.INT),
|
||||
column("firstname", "fn", DataTypes.TEXT));
|
||||
|
||||
WithNullableEmbeddedType target = mappingCassandraConverter.read(WithNullableEmbeddedType.class, source);
|
||||
assertThat(target.nested).isEqualTo(new EmbeddedWithSimpleTypes("fn", 30, null));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void readPrefixedEmbeddedType() {
|
||||
|
||||
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("prefixage", 30, DataTypes.INT),
|
||||
column("prefixfirstname", "fn", DataTypes.TEXT));
|
||||
|
||||
WithPrefixedNullableEmbeddedType target = mappingCassandraConverter.read(WithPrefixedNullableEmbeddedType.class, source);
|
||||
assertThat(target.nested).isEqualTo(new EmbeddedWithSimpleTypes("fn", 30, null));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void readEmbeddedTypeWhenSourceDoesNotContainValues() {
|
||||
|
||||
Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT));
|
||||
|
||||
WithNullableEmbeddedType target = mappingCassandraConverter.read(WithNullableEmbeddedType.class, source);
|
||||
assertThat(target.nested).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -36,12 +37,12 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.Column;
|
||||
import org.springframework.data.cassandra.core.mapping.Element;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.Tuple;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
@@ -378,6 +379,28 @@ public class QueryMapperUnitTests {
|
||||
this.mappingContext.getRequiredPersistentEntity(Person.class)));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldMapEmbeddedType() {
|
||||
|
||||
Filter filter = Filter.from(Criteria.where("nested.firstname").is("spring"));
|
||||
|
||||
Filter mappedObject = this.queryMapper.getMappedObject(filter,
|
||||
this.mappingContext.getRequiredPersistentEntity(WithNullableEmbeddedType.class));
|
||||
|
||||
assertThat(mappedObject.iterator().next().getColumnName()).isEqualTo(ColumnName.from("firstname"));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldMapPrefixedEmbeddedType() {
|
||||
|
||||
Filter filter = Filter.from(Criteria.where("nested.firstname").is("spring"));
|
||||
|
||||
Filter mappedObject = this.queryMapper.getMappedObject(filter,
|
||||
this.mappingContext.getRequiredPersistentEntity(WithPrefixedNullableEmbeddedType.class));
|
||||
|
||||
assertThat(mappedObject.iterator().next().getColumnName()).isEqualTo(ColumnName.from("prefixfirstname"));
|
||||
}
|
||||
|
||||
static class Person {
|
||||
|
||||
@Id String id;
|
||||
@@ -413,4 +436,27 @@ public class QueryMapperUnitTests {
|
||||
enum State {
|
||||
Active, Inactive;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithPrefixedNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
String firstname;
|
||||
Integer age;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.cql.Ordering;
|
||||
@@ -44,16 +43,7 @@ import org.springframework.data.cassandra.core.cql.keyspace.ColumnSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification.ColumnFunction;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraType;
|
||||
import org.springframework.data.cassandra.core.mapping.Element;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
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.Tuple;
|
||||
import org.springframework.data.cassandra.core.mapping.*;
|
||||
import org.springframework.data.cassandra.domain.AllPossibleTypes;
|
||||
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -76,6 +66,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
*
|
||||
* @author Matthew T. Adams
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SchemaFactoryUnitTests {
|
||||
|
||||
@@ -827,4 +818,61 @@ public class SchemaFactoryUnitTests {
|
||||
@Id String id;
|
||||
Map<String, TupleValue> untyped;
|
||||
}
|
||||
|
||||
@Table
|
||||
@Data
|
||||
static class TypeWithEmbedded {
|
||||
|
||||
@Id String id;
|
||||
@Embedded.Nullable EmbeddedTpe name;
|
||||
@Embedded.Nullable(prefix = "a") EmbeddedTpe alias;
|
||||
@Indexed @Embedded.Nullable(prefix = "aego") EmbeddedTpe alterEgo;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EmbeddedTpe {
|
||||
|
||||
@Indexed String firstname;
|
||||
String lastname;
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void createTableSpecificationShouldConsiderEmbeddedType() {
|
||||
|
||||
CreateTableSpecification specification = getCreateTableSpecificationFor(TypeWithEmbedded.class);
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(getColumn("firstname", specification)).isNotNull();
|
||||
assertThat(getColumn("lastname", specification)).isNotNull();
|
||||
assertThat(getColumn("afirstname", specification)).isNotNull();
|
||||
assertThat(getColumn("alastname", specification)).isNotNull();
|
||||
assertThat(getColumn("aegofirstname", specification)).isNotNull();
|
||||
assertThat(getColumn("aegolastname", specification)).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void createIndexSpecificationShouldConsiderEmbeddedType() {
|
||||
|
||||
List<CreateIndexSpecification> specifications = schemaFactory
|
||||
.getCreateIndexSpecificationsFor(ctx.getRequiredPersistentEntity(TypeWithEmbedded.class));
|
||||
|
||||
CreateIndexSpecification firstname = getSpecificationFor("firstname", specifications);
|
||||
|
||||
assertThat(firstname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("firstname"));
|
||||
assertThat(firstname.getName()).isNull();
|
||||
assertThat(firstname.getColumnFunction()).isEqualTo(ColumnFunction.NONE);
|
||||
|
||||
CreateIndexSpecification afirstname = getSpecificationFor("afirstname", specifications);
|
||||
|
||||
assertThat(afirstname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("afirstname"));
|
||||
assertThat(afirstname.getName()).isNull();
|
||||
assertThat(afirstname.getColumnFunction()).isEqualTo(ColumnFunction.NONE);
|
||||
|
||||
CreateIndexSpecification aegofirstname = getSpecificationFor("aegofirstname", specifications);
|
||||
assertThat(aegofirstname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("aegofirstname"));
|
||||
|
||||
CreateIndexSpecification aegolastname = getSpecificationFor("aegolastname", specifications);
|
||||
assertThat(aegolastname.getColumnName()).isEqualTo(CqlIdentifier.fromCql("aegolastname"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.core.convert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.util.Collections;
|
||||
@@ -31,12 +33,13 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
|
||||
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
|
||||
import org.springframework.data.cassandra.core.mapping.Column;
|
||||
import org.springframework.data.cassandra.core.mapping.Element;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
import org.springframework.data.cassandra.core.mapping.Tuple;
|
||||
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
|
||||
import org.springframework.data.cassandra.core.mapping.UserTypeResolver;
|
||||
@@ -46,13 +49,11 @@ import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
import com.datastax.oss.driver.api.core.type.DataTypes;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link UpdateMapper}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class UpdateMapperUnitTests {
|
||||
@@ -259,6 +260,26 @@ public class UpdateMapperUnitTests {
|
||||
() -> this.updateMapper.getMappedObject(Update.empty().set("tuple.zip", "bar"), this.persistentEntity));
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldMapEmbeddedEntity() {
|
||||
|
||||
Update update = this.updateMapper.getMappedObject(Update.empty().set("nested.firstname", "spring"),
|
||||
mappingContext.getRequiredPersistentEntity(WithNullableEmbeddedType.class));
|
||||
|
||||
assertThat(update.getUpdateOperations()).hasSize(1);
|
||||
assertThat(update.toString()).startsWith("firstname = 'spring'");
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void shouldMapPrefixedEmbeddedEntity() {
|
||||
|
||||
Update update = this.updateMapper.getMappedObject(Update.empty().set("nested.firstname", "spring"),
|
||||
mappingContext.getRequiredPersistentEntity(WithPrefixedNullableEmbeddedType.class));
|
||||
|
||||
assertThat(update.getUpdateOperations()).hasSize(1);
|
||||
assertThat(update.toString()).startsWith("prefixfirstname = 'spring'");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Person {
|
||||
|
||||
@@ -295,4 +316,30 @@ public class UpdateMapperUnitTests {
|
||||
static class Manufacturer {
|
||||
String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
@Embedded.Nullable EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class WithPrefixedNullableEmbeddedType {
|
||||
|
||||
@Id String id;
|
||||
|
||||
// @Indexed -> index for all properties of nested
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
@Indexed // single property index (IndexSpecificationFactory) | sassi index etc. :'(
|
||||
String firstname;
|
||||
Integer age;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ package org.springframework.data.cassandra.repository;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -30,16 +32,19 @@ import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.config.SchemaAction;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.cql.generator.CreateIndexCqlGenerator;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateIndexSpecification;
|
||||
import org.springframework.data.cassandra.core.mapping.Embedded;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
import org.springframework.data.cassandra.core.query.CassandraPageRequest;
|
||||
import org.springframework.data.cassandra.domain.AddressType;
|
||||
import org.springframework.data.cassandra.domain.Person;
|
||||
@@ -64,6 +69,7 @@ import com.datastax.oss.driver.api.core.CqlSession;
|
||||
* Integration tests for query derivation through {@link PersonRepository}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@@ -77,18 +83,19 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
|
||||
|
||||
@Override
|
||||
protected Set<Class<?>> getInitialEntitySet() {
|
||||
return Collections.singleton(Person.class);
|
||||
return new HashSet<>(Arrays.asList(Person.class, PersonWithEmbedded.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaAction getSchemaAction() {
|
||||
return SchemaAction.RECREATE_DROP_UNUSED;
|
||||
return SchemaAction.CREATE;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired CassandraOperations template;
|
||||
@Autowired CqlSession session;
|
||||
@Autowired PersonRepository personRepository;
|
||||
@Autowired EmbeddedPersonRepository personWithEmbeddedRepository;
|
||||
|
||||
private Person walter;
|
||||
private Person skyler;
|
||||
@@ -368,6 +375,20 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
|
||||
assertThat(personRepository.existsByLastname("Schrader")).isFalse();
|
||||
}
|
||||
|
||||
@Test // DATACASS-167
|
||||
public void derivedQueryOnPropertyOfEmbeddedEntity() {
|
||||
|
||||
PersonWithEmbedded source = new PersonWithEmbedded();
|
||||
source.id = "id-1";
|
||||
source.name = new Name();
|
||||
source.name.firstname = "spring";
|
||||
source.name.lastname = "data";
|
||||
|
||||
personWithEmbeddedRepository.save(source);
|
||||
|
||||
assertThat(personWithEmbeddedRepository.findByName_Firstname("spring")).isEqualTo(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@@ -439,4 +460,28 @@ public class QueryDerivationIntegrationTests extends AbstractSpringDataEmbeddedC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
static interface EmbeddedPersonRepository extends CassandraRepository<PersonWithEmbedded, String> {
|
||||
|
||||
PersonWithEmbedded findByName_Firstname(String firstname);
|
||||
|
||||
}
|
||||
|
||||
@Table
|
||||
@Data
|
||||
static class PersonWithEmbedded {
|
||||
|
||||
@Id String id;
|
||||
@Embedded.Nullable Name name;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Name {
|
||||
|
||||
@Indexed String firstname;
|
||||
String lastname;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,13 @@ import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.core.convert.SchemaFactory;
|
||||
import org.springframework.data.cassandra.core.cql.SessionCallback;
|
||||
import org.springframework.data.cassandra.core.cql.generator.CreateTableCqlGenerator;
|
||||
import org.springframework.data.cassandra.core.cql.generator.CreateUserTypeCqlGenerator;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateTableSpecification;
|
||||
import org.springframework.data.cassandra.core.cql.keyspace.CreateUserTypeSpecification;
|
||||
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.EmbeddedEntityOperations;
|
||||
|
||||
import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata;
|
||||
|
||||
@@ -32,6 +36,7 @@ import com.datastax.oss.driver.api.core.metadata.schema.TableMetadata;
|
||||
* scenarios.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SchemaTestUtils {
|
||||
|
||||
@@ -43,10 +48,36 @@ public class SchemaTestUtils {
|
||||
*/
|
||||
public static void potentiallyCreateTableFor(Class<?> entityClass, CassandraOperations operations) {
|
||||
|
||||
CassandraMappingContext mappingContext = operations.getConverter().getMappingContext();
|
||||
CassandraPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass);
|
||||
|
||||
potentiallyCreateTableFor(persistentEntity, operations, new SchemaFactory(operations.getConverter()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a table and UDTs for {@code entityClass} if it not exists.
|
||||
*
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @param operations must not be {@literal null}.
|
||||
*/
|
||||
public static void createTableAndTypes(Class<?> entityClass, CassandraOperations operations) {
|
||||
|
||||
CassandraMappingContext mappingContext = operations.getConverter().getMappingContext();
|
||||
CassandraPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(entityClass);
|
||||
SchemaFactory schemaFactory = new SchemaFactory(operations.getConverter());
|
||||
|
||||
potentiallyCreateUdtFor(persistentEntity, operations, schemaFactory);
|
||||
potentiallyCreateTableFor(persistentEntity, operations, schemaFactory);
|
||||
}
|
||||
|
||||
public static void potentiallyCreateUdtFor(Class<?> entityType, CassandraOperations operations) {
|
||||
potentiallyCreateUdtFor(operations.getConverter().getMappingContext().getRequiredPersistentEntity(entityType),
|
||||
operations, new SchemaFactory(operations.getConverter()));
|
||||
}
|
||||
|
||||
private static void potentiallyCreateTableFor(CassandraPersistentEntity<?> persistentEntity,
|
||||
CassandraOperations operations, SchemaFactory schemaFactory) {
|
||||
|
||||
operations.getCqlOperations().execute((SessionCallback<Object>) session -> {
|
||||
|
||||
Optional<TableMetadata> table = session.getKeyspace().flatMap(it -> session.getMetadata().getKeyspace(it))
|
||||
@@ -60,6 +91,32 @@ public class SchemaTestUtils {
|
||||
});
|
||||
}
|
||||
|
||||
private static void potentiallyCreateUdtFor(CassandraPersistentEntity<?> persistentEntity,
|
||||
CassandraOperations operations, SchemaFactory schemaFactory) {
|
||||
|
||||
if (persistentEntity.isUserDefinedType()) {
|
||||
|
||||
CreateUserTypeSpecification udtspec = schemaFactory.getCreateUserTypeSpecificationFor(persistentEntity)
|
||||
.ifNotExists();
|
||||
operations.getCqlOperations().execute(CreateUserTypeCqlGenerator.toCql(udtspec));
|
||||
|
||||
} else {
|
||||
|
||||
for (CassandraPersistentProperty property : persistentEntity) {
|
||||
if (property.isEntity()) {
|
||||
if (property.isEmbedded()) {
|
||||
potentiallyCreateUdtFor(
|
||||
new EmbeddedEntityOperations(operations.getConverter().getMappingContext()).getEntity(property),
|
||||
operations, schemaFactory);
|
||||
} else {
|
||||
potentiallyCreateUdtFor(operations.getConverter().getMappingContext().getRequiredPersistentEntity(property),
|
||||
operations, schemaFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate table for {@code entityClass}.
|
||||
*
|
||||
|
||||
@@ -344,6 +344,63 @@ public class LoginEvent {
|
||||
----
|
||||
====
|
||||
|
||||
[[mapping.embedded-entities]]
|
||||
=== Embedded Entity Support
|
||||
|
||||
Embedded entities are used to have value objects in your java data model who's properties are flattened out into the table.
|
||||
In the following example you see, that `User.name` is annotated with `@Embedded`.
|
||||
The consequence of this is the properties of `UserName` are folded into the `user` table which consists of 3 columns (`user_id`, `firstname`, `lastname`).
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Embedded entities may only contain simple property types. It is not possible to nest an embedded entity into another embedded one.
|
||||
====
|
||||
|
||||
However, if the `firstname` and `lastname` column values are actually `null` within the result set, the entire property `name` will be set to `null` according to the `onEmpty` of `@Embedded`, which ``null``s objects when all nested properties are `null`. +
|
||||
Opposite to this behavior `USE_EMPTY` tries to create a new instance using either a default constructor or one that accepts nullable parameter values from the result set.
|
||||
|
||||
.Sample Code of embedding objects
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
public class User {
|
||||
|
||||
@PrimaryKey("user_id")
|
||||
private String userId;
|
||||
|
||||
@Embedded(onEmpty = USE_NULL) <1>
|
||||
UserName name;
|
||||
}
|
||||
|
||||
public class UserName {
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
}
|
||||
----
|
||||
<1> ``Null``s `embeddedEntity` if `name` in `null`. Use `USE_EMPTY` to instantiate `embeddedEntity` with a potential `null` value for the `name` property.
|
||||
====
|
||||
|
||||
If you need a value object multiple times in an entity, this can be achieved with the optional `prefix` element of the `@Embedded` annotation.
|
||||
This element represents a prefix and is prepend for each column name in the embedded object.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Make use of the shortcuts `@Embedded.Nullable` & `@Embedded.Empty` for `@Embedded(onEmpty = USE_NULL)` and `@Embedded(onEmpty = USE_EMPTY)` to reduce verbosity and simultaneously set JSR-305 `@javax.annotation.Nonnull` accordingly.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
public class MyEntity {
|
||||
|
||||
@Id
|
||||
Integer id;
|
||||
|
||||
@Embedded.Nullable <1>
|
||||
EmbeddedEntity embeddedEntity;
|
||||
}
|
||||
----
|
||||
<1> Shortcut for `@Embedded(onEmpty = USE_NULL)`.
|
||||
====
|
||||
|
||||
[[mapping.usage-annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
@@ -443,6 +500,11 @@ include::../{example-root}/mapping/PersonWithIndexes.java[tags=class]
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `@Indexed` annotation can be applied to single properties of embedded entities or along side with the `@Embedded` annotation, in which case all properties of the embedded are indexed.
|
||||
====
|
||||
|
||||
CAUTION: Index creation on session initialization may have a severe performance impact on application startup.
|
||||
|
||||
include::./converters.adoc[]
|
||||
|
||||
Reference in New Issue
Block a user