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:
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user