DATACASS-623 - Add support for read-only properties.

We now support read-only properties by annotating such properties with @ReadOnlyProperty. Read-only properties are read from results but never written (i.e. through INSERT or UPDATE) back to Cassandra.

Read-only properties can still be updated by using the Update/Query API.

class Person {

  @Id String id;

  String firstname;
  String lastname;
  @ReadOnlyProperty String externallyUpdatedProperty;
}

CassandraTemplate template = …
Person person = …
template.insert(person);
This commit is contained in:
Mark Paluch
2019-01-31 10:01:01 +01:00
parent 9e825d376e
commit 3a062afafb
5 changed files with 67 additions and 15 deletions

View File

@@ -435,6 +435,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
for (CassandraPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = getWriteValue(property, propertyAccessor);
if (log.isDebugEnabled()) {
@@ -475,6 +479,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
for (CassandraPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = getWriteValue(property, propertyAccessor);
if (property.isCompositePrimaryKey()) {
@@ -643,6 +651,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
for (CassandraPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = getWriteValue(property, propertyAccessor);
if (log.isDebugEnabled()) {

View File

@@ -15,9 +15,12 @@
*/
package org.springframework.data.cassandra.core.convert;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.springframework.data.cassandra.test.util.RowMockUtil.column;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.cassandra.test.util.RowMockUtil.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.Collections;
@@ -25,9 +28,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -35,7 +35,7 @@ import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.core.mapping.UserDefinedType;
@@ -61,7 +61,8 @@ public class MappingCassandraConverterUDTUnitTests {
@Rule public final ExpectedException expectedException = ExpectedException.none();
@Mock UserTypeResolver userTypeResolver;
UserType manufacturer = UserTypeBuilder.forName("manufacturer").withField("name", DataType.varchar()).build();
UserType manufacturer = UserTypeBuilder.forName("manufacturer").withField("name", DataType.varchar())
.withField("displayname", DataType.varchar()).build();
UserType currency = UserTypeBuilder.forName("mycurrency").withField("currency", DataType.varchar()).build();
Row rowMock;
@@ -82,10 +83,10 @@ public class MappingCassandraConverterUDTUnitTests {
when(userTypeResolver.resolveType(CqlIdentifier.of("currency"))).thenReturn(currency);
}
@Test // DATACASS-487
@Test // DATACASS-487, DATACASS-623
public void shouldReadMappedUdtInMap() {
UDTValue key = manufacturer.newValue().setString("name", "a good one");
UDTValue key = manufacturer.newValue().setString("name", "a good one").setString("displayname", "my displayName");
UDTValue value1 = currency.newValue().setString("currency", "EUR");
UDTValue value2 = currency.newValue().setString("currency", "USD");
@@ -93,21 +94,22 @@ public class MappingCassandraConverterUDTUnitTests {
map.put(key, Arrays.asList(value1, value2));
rowMock = RowMockUtil.newRowMock(column("acceptedCurrencies", map, DataType.map(manufacturer, DataType.list(currency))));
rowMock = RowMockUtil
.newRowMock(column("acceptedCurrencies", map, DataType.map(manufacturer, DataType.list(currency))));
Supplier supplier = mappingCassandraConverter.read(Supplier.class, rowMock);
assertThat(supplier.getAcceptedCurrencies()).isNotEmpty();
List<Currency> currencies = supplier.getAcceptedCurrencies().get(new Manufacturer("a good one"));
List<Currency> currencies = supplier.getAcceptedCurrencies().get(new Manufacturer("a good one", "my displayName"));
assertThat(currencies).contains(new Currency("EUR"), new Currency("USD"));
}
@Test // DATACASS-487
@Test // DATACASS-487, DATACASS-623
public void shouldWriteMappedUdtInMap() {
Map<Manufacturer, List<Currency>> currencies = Collections.singletonMap(new Manufacturer("a good one"),
Map<Manufacturer, List<Currency>> currencies = Collections.singletonMap(new Manufacturer("a good one", "foo"),
Arrays.asList(new Currency("EUR"), new Currency("USD")));
Supplier supplier = new Supplier(currencies);
@@ -116,14 +118,17 @@ public class MappingCassandraConverterUDTUnitTests {
mappingCassandraConverter.write(supplier, insert);
assertThat(insert.toString()).contains("VALUES ({{name:'a good one'}:[{currency:'EUR'},{currency:'USD'}]}");
assertThat(insert.toString())
.contains("VALUES ({{name:'a good one',displayname:NULL}:[{currency:'EUR'},{currency:'USD'}]}");
}
@UserDefinedType
@Data
@AllArgsConstructor
private static class Manufacturer {
String name;
@ReadOnlyProperty String displayName;
}
@UserDefinedType

View File

@@ -42,6 +42,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.SpringVersion;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import org.springframework.data.cassandra.core.cql.PrimaryKeyType;
import org.springframework.data.cassandra.core.mapping.BasicMapId;
@@ -964,6 +965,34 @@ public class MappingCassandraConverterUnitTests {
assertThat(insert.toString()).isEqualTo("INSERT INTO table (firstname,lastname) VALUES ('Foo','Bar');");
}
@Test // DATACASS-623
public void insertShouldSkipTransientReadProperties() {
WithTransient withTransient = new WithTransient();
withTransient.firstname = "Foo";
withTransient.computedName = "FooBar";
Insert insert = QueryBuilder.insertInto("table");
this.mappingCassandraConverter.write(withTransient, insert);
assertThat(insert.toString()).isEqualTo("INSERT INTO table (firstname) VALUES ('Foo');");
}
@Test // DATACASS-623
public void updateShouldSkipTransientReadProperties() {
WithTransient withTransient = new WithTransient();
withTransient.firstname = "Foo";
withTransient.computedName = "FooBar";
Update update = QueryBuilder.update("table");
this.mappingCassandraConverter.write(withTransient, update);
assertThat(update.toString()).isEqualTo("UPDATE table SET firstname='Foo',lastname=null WHERE id=null;");
}
@SuppressWarnings("unchecked")
private static <T> List<T> getListValue(Insert statement) {
@@ -1233,5 +1262,6 @@ public class MappingCassandraConverterUnitTests {
String firstname;
String lastname;
@Transient String displayName;
@ReadOnlyProperty String computedName;
}
}

View File

@@ -3,6 +3,10 @@
This chapter summarizes changes and new features for each release.
[[new-features.2-2-0]]
== What's new in Spring Data for Apache Cassandra 2.2
* Read-only properties annotated with `@ReadOnlyProperty` to exclude non-writable properties from entity-bound `INSERT` and `UPDATE` operations.
[[new-features.2-1-0]]
== What's new in Spring Data for Apache Cassandra 2.1
* New annotations for `@CountQuery` and `@ExistsQuery`.

View File

@@ -414,6 +414,7 @@ to indicate either a single or a composite (compound) primary key. If used on a
* `@PrimaryKeyClass`: Applied at the class level to indicate that this class is a compound primary key class.
Must be referenced with `@PrimaryKey` in the entity class.
* `@Transient`: By default, all private fields are mapped to the row. This annotation excludes the field
* `@ReadOnlyProperty`: Applies at the field level to mark a property as read-only. Entity-bound insert and update statements do not include this property.
where it is applied from being stored in the database.
* `@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.