DATACASS-167 - Polishing.
Add value shortcut to Embedded.Nullable and Embedded.Empty for improved annotation usage. Move isEntity check into PersistentProperty.isEmbedded as both checks are issued always together. Tweak documentation. Original pull request: #173.
This commit is contained in:
@@ -434,7 +434,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
|
||||
Assert.notNull(source, "Value must not be null");
|
||||
|
||||
// TODO
|
||||
Class<?> beanClassLoaderClass = transformClassToBeanClassLoaderClass(source.getClass());
|
||||
|
||||
CassandraPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(beanClassLoaderClass);
|
||||
@@ -499,7 +498,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value != null && property.isEmbedded() && property.isEntity()) {
|
||||
if (value != null && property.isEmbedded()) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Mapping embedded property [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
@@ -653,7 +652,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
log.debug("Adding udt.value [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
}
|
||||
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Mapping embedded property [{}] - [{}]", property.getRequiredColumnName(), value);
|
||||
@@ -918,8 +917,8 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value to read for the given {@link CassandraPersistentProperty} from
|
||||
* {@link BasicCassandraRowValueProvider} and perform optionally a conversion of collection element types.
|
||||
* Retrieve the value to read for the given {@link CassandraPersistentProperty} from {@link CassandraValueProvider}
|
||||
* and perform optionally a conversion of collection element types.
|
||||
*
|
||||
* @param valueProvider the row.
|
||||
* @param property the property.
|
||||
@@ -934,7 +933,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
return doReadEntity(keyEntity, valueProvider);
|
||||
}
|
||||
|
||||
if (property.isEntity() && property.isEmbedded()) {
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
CassandraPersistentEntity<?> targetEntity = embeddedEntityOperations.getEntity(property);
|
||||
return isNullEmbedded(targetEntity, property, valueProvider) ? null : doReadEntity(targetEntity, valueProvider);
|
||||
@@ -959,7 +958,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
|
||||
private boolean isNullEmbedded(CassandraPersistentEntity<?> entity, CassandraPersistentProperty property,
|
||||
CassandraValueProvider valueProvider) {
|
||||
|
||||
if (OnEmpty.USE_EMPTY.equals(property.findAnnotation(Embedded.class).onEmpty())) {
|
||||
if (OnEmpty.USE_EMPTY.equals(property.getRequiredAnnotation(Embedded.class).onEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -535,7 +535,7 @@ public class QueryMapper {
|
||||
leafProperty = parentEntity.getPersistentProperty(p.getName());
|
||||
parentEntity = null;
|
||||
}
|
||||
if (p.isEmbedded() && p.isEntity()) {
|
||||
if (p.isEmbedded()) {
|
||||
embedded = true;
|
||||
parentEntity = new EmbeddedEntityOperations(mappingContext).getEntity(p);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public class SchemaFactory {
|
||||
primaryKeyProperty.getPrimaryKeyOrdering());
|
||||
}
|
||||
}
|
||||
} else if (property.isEmbedded() && property.isEntity()) {
|
||||
} else if (property.isEmbedded()) {
|
||||
|
||||
CassandraPersistentEntity<?> embeddedEntity = embeddedEntityOperations.getEntity(property);
|
||||
|
||||
@@ -167,7 +167,6 @@ public class SchemaFactory {
|
||||
DataType dataType = getDataType(embeddedProperty);
|
||||
specification.column(embeddedProperty.getRequiredColumnName(), dataType);
|
||||
}
|
||||
|
||||
} else {
|
||||
DataType type = UserTypeUtil.potentiallyFreeze(getDataType(property));
|
||||
|
||||
@@ -248,7 +247,7 @@ public class SchemaFactory {
|
||||
if (property.isCompositePrimaryKey()) {
|
||||
indexes.addAll(getCreateIndexSpecificationsFor(mappingContext.getRequiredPersistentEntity(property)));
|
||||
}
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
if (property.isAnnotationPresent(Indexed.class)) {
|
||||
Indexed indexed = property.findAnnotation(Indexed.class);
|
||||
@@ -281,7 +280,7 @@ public class SchemaFactory {
|
||||
|
||||
for (CassandraPersistentProperty property : entity) {
|
||||
|
||||
if (property.isEmbedded() && property.isEntity()) {
|
||||
if (property.isEmbedded()) {
|
||||
|
||||
CassandraPersistentEntity<?> embeddedEntity = embeddedEntityOperations.getEntity(property);
|
||||
for (CassandraPersistentProperty embeddedProperty : embeddedEntity) {
|
||||
|
||||
@@ -163,7 +163,7 @@ public interface CassandraPersistentProperty
|
||||
* @since 3.0
|
||||
*/
|
||||
default boolean isEmbedded() {
|
||||
return findAnnotation(Embedded.class) != null;
|
||||
return findAnnotation(Embedded.class) != null && isEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,13 +26,19 @@ import javax.annotation.meta.When;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The annotation to configure a value object as embedded in the current table.
|
||||
* <p />
|
||||
* Depending on the {@link OnEmpty value} of {@link #onEmpty()} the property is set to {@literal null} or an empty
|
||||
* instance in the case all embedded values are {@literal null} when reading from the result set.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
@Target(value = { ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD })
|
||||
public @interface Embedded {
|
||||
|
||||
/**
|
||||
* Set the load strategy for the embedded object if all contained fields yield {@literal null} values.
|
||||
* <p />
|
||||
@@ -51,7 +57,6 @@ public @interface Embedded {
|
||||
* Load strategy to be used {@link Embedded#onEmpty()}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.1
|
||||
*/
|
||||
enum OnEmpty {
|
||||
USE_NULL, USE_EMPTY
|
||||
@@ -60,27 +65,17 @@ public @interface Embedded {
|
||||
/**
|
||||
* Shortcut for a nullable embedded property.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* @Embedded.Nullable
|
||||
* private Address address;
|
||||
* </code>
|
||||
* <pre class="code">
|
||||
* @Embedded.Nullable private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* @Embedded(onEmpty = USE_NULL)
|
||||
* @javax.annotation.Nonnull(when = When.MAYBE)
|
||||
* private Address address;
|
||||
*
|
||||
* </code>
|
||||
* <pre class="code">
|
||||
* @Embedded(onEmpty = USE_NULL) @javax.annotation.Nonnull(when = When.MAYBE) private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_NULL)
|
||||
@@ -95,32 +90,28 @@ public @interface Embedded {
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* @return value for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for an empty embedded property.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* @Embedded.Empty
|
||||
* private Address address;
|
||||
* </code>
|
||||
* <pre class="code">
|
||||
* @Embedded.Empty private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* as alternative to the more verbose
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
*
|
||||
* @Embedded(onEmpty = USE_EMPTY)
|
||||
* @javax.annotation.Nonnull(when = When.NEVER)
|
||||
* private Address address;
|
||||
*
|
||||
* </code>
|
||||
* <pre class="code">
|
||||
* @Embedded(onEmpty = USE_EMPTY) @javax.annotation.Nonnull(when = When.NEVER) private Address address;
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see Embedded#onEmpty()
|
||||
*/
|
||||
@Embedded(onEmpty = OnEmpty.USE_EMPTY)
|
||||
@@ -135,5 +126,11 @@ public @interface Embedded {
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String prefix() default "";
|
||||
|
||||
/**
|
||||
* @return value for columns in the embedded value object. An empty {@link String} by default.
|
||||
*/
|
||||
@AliasFor(annotation = Embedded.class, attribute = "prefix")
|
||||
String value() default "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,23 +29,27 @@ 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.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlIdentifier;
|
||||
|
||||
/**
|
||||
* Support methods to obtain {@link PersistentProperty} and {@link PersistentEntity} for embedded properties.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.0
|
||||
* @see Embedded
|
||||
*/
|
||||
public class EmbeddedEntityOperations {
|
||||
|
||||
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
|
||||
|
||||
public EmbeddedEntityOperations(MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
|
||||
public EmbeddedEntityOperations(
|
||||
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ public class EmbeddedEntityOperations {
|
||||
static class PrefixedCassandraPersistentEntity<T> implements CassandraPersistentEntity<T> {
|
||||
|
||||
private final String prefix;
|
||||
private CassandraPersistentEntity<T> delegate;
|
||||
private final CassandraPersistentEntity<T> delegate;
|
||||
|
||||
public PrefixedCassandraPersistentEntity(String prefix, CassandraPersistentEntity<T> delegate) {
|
||||
|
||||
@@ -169,7 +173,10 @@ public class EmbeddedEntityOperations {
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getPersistentProperty(String name) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, delegate.getPersistentProperty(name));
|
||||
|
||||
CassandraPersistentProperty property = delegate.getPersistentProperty(name);
|
||||
|
||||
return property == null ? null : new PrefixedCassandraPersistentProperty(prefix, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,7 +187,10 @@ public class EmbeddedEntityOperations {
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CassandraPersistentProperty getPersistentProperty(Class<? extends Annotation> annotationType) {
|
||||
return new PrefixedCassandraPersistentProperty(prefix, delegate.getPersistentProperty(annotationType));
|
||||
|
||||
CassandraPersistentProperty property = delegate.getPersistentProperty(annotationType);
|
||||
|
||||
return property == null ? null : new PrefixedCassandraPersistentProperty(prefix, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -337,7 +347,6 @@ public class EmbeddedEntityOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
@org.springframework.lang.Nullable
|
||||
public CqlIdentifier getColumnName() {
|
||||
return CqlIdentifier.fromInternal(prefix + delegate.getColumnName().asInternal());
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ public class MappingCassandraConverterUnitTests {
|
||||
|
||||
String id;
|
||||
|
||||
@Embedded.Nullable(prefix = "prefix") EmbeddedWithSimpleTypes nested;
|
||||
@Embedded.Nullable("prefix") EmbeddedWithSimpleTypes nested;
|
||||
}
|
||||
|
||||
@ToString
|
||||
|
||||
@@ -337,8 +337,7 @@ public class UpdateMapperUnitTests {
|
||||
@Data
|
||||
static class EmbeddedWithSimpleTypes {
|
||||
|
||||
@Indexed // single property index (IndexSpecificationFactory) | sassi index etc. :'(
|
||||
String firstname;
|
||||
@Indexed String firstname;
|
||||
Integer age;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,11 +70,6 @@ public class SchemaTestUtils {
|
||||
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) {
|
||||
|
||||
@@ -103,15 +98,18 @@ public class SchemaTestUtils {
|
||||
} 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);
|
||||
}
|
||||
|
||||
if (!property.isEntity()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.isEmbedded()) {
|
||||
potentiallyCreateUdtFor(
|
||||
new EmbeddedEntityOperations(operations.getConverter().getMappingContext()).getEntity(property),
|
||||
operations, schemaFactory);
|
||||
} else {
|
||||
potentiallyCreateUdtFor(operations.getConverter().getMappingContext().getRequiredPersistentEntity(property),
|
||||
operations, schemaFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ This chapter summarizes changes and new features for each release.
|
||||
* Upgrade to Cassandra Driver version 4. See the <<cassandra.migration.2.x-to-3.x,2.x to 3.x migration guide for details>>.
|
||||
* Support for `NamingStrategy`.
|
||||
* Support for frozen collections and UDT columns in schema creation.
|
||||
* Support for <<mapping.embedded-entities,`@Embedded` properties>>.
|
||||
|
||||
[[new-features.2-2-0]]
|
||||
== What's new in Spring Data for Apache Cassandra 2.2
|
||||
|
||||
@@ -347,13 +347,14 @@ 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.
|
||||
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 the properties of `UserName` are folded into the `user` table which consists of 3 columns (`user_id`, `firstname`, `lastname`).
|
||||
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`).
|
||||
|
||||
[WARNING]
|
||||
[NOTE]
|
||||
====
|
||||
Embedded entities may only contain simple property types. It is not possible to nest an embedded entity into another embedded one.
|
||||
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`. +
|
||||
@@ -377,17 +378,19 @@ public class UserName {
|
||||
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.
|
||||
<1> Property is `null` if `firstname` and `lastname` are `null`.
|
||||
Use `onEmpty=USE_EMPTY` to instantiate `UserName` with a potential `null` value for its properties.
|
||||
====
|
||||
|
||||
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.
|
||||
You can embed a value object multiple times in an entity by using the optional `prefix` element of the `@Embedded` annotation.
|
||||
This element represents a prefix and is prepended to each column name in the embedded object.
|
||||
Note that properties will overwrite each other if multiple properties render to the same column name.
|
||||
|
||||
[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.
|
||||
Make use of the shortcuts `@Embedded.Nullable` and `@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]
|
||||
[source,java]
|
||||
----
|
||||
public class MyEntity {
|
||||
|
||||
@@ -428,6 +431,9 @@ 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.
|
||||
* `@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.
|
||||
* `@Indexed`: Applied at the field level.
|
||||
Describes the index to be created at session initialization.
|
||||
* `@SASI`: Applied at the field level.
|
||||
|
||||
Reference in New Issue
Block a user