DATACOUCH-145 - Use Id/Field annotations from Couchbase SDK
The @Field annotation has been removed in favor of the one from the Couchbase SDK. Similarly, CouchbasePersistentEntity now recognize the SDK's @Id annotation. Note that if both Spring Data and Couchbase @Id are present (on two distinct fields), the one from Spring Data will be taken into account.
This commit is contained in:
@@ -52,7 +52,7 @@ import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.Field;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.couchbase.repository;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.core.mapping.Field;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
/**
|
||||
* An entity used to test conversion of parameters in query derivations.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.data.couchbase.repository.cdi;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.core.mapping.Field;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
|
||||
@@ -6,17 +6,23 @@ This chapter describes how to model Entities and explains their counterpart repr
|
||||
[[basics]]
|
||||
== Documents and Fields
|
||||
|
||||
All entities should be annotated with the `@Document` annotation. Also, every field in the entity should be annotated with the `@Field` annotation. While this is - strictly speaking - optional, it helps to reduce edge cases and clearly shows the intent and design of the entity.
|
||||
All entities should be annotated with the `@Document` annotation.
|
||||
|
||||
There is also a special `@Id` annotation which needs to be always in place. Best practice is to also name the property `id`. Here is a very simple `User` entity:
|
||||
Also, every field in the entity should be annotated with the `@Field` annotation from the Couchbase SDK. While this is - strictly speaking - optional, it helps to reduce edge cases and clearly shows the intent and design of the entity. It can also be used to store the field under a different name.
|
||||
|
||||
There is also a special `@Id` annotation which needs to be always in place. Best practice is to also name the property `id`.
|
||||
|
||||
TIP: Both the Couchbase SDK and Spring Data define their own `@Id` annotation. Either can be used (the Spring Data one will get priority if both are found on different fields).
|
||||
|
||||
Here is a very simple `User` entity:
|
||||
|
||||
.A simple Document with Fields
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.data.annotation.Id;
|
||||
import com.couchbase.client.java.repository.annotation.Id;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.Field;
|
||||
|
||||
@Document
|
||||
public class User {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.data.couchbase.core.mapping;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Id;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -61,6 +63,38 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
context.setRootObject(applicationContext);
|
||||
}
|
||||
|
||||
// DATACOUCH-145: allows SDK's @Id annotation to be used
|
||||
@Override
|
||||
protected CouchbasePersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(CouchbasePersistentProperty property) {
|
||||
if (!property.isIdProperty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.hasIdProperty()) {
|
||||
return property;
|
||||
}
|
||||
|
||||
//check existing ID vs new candidate
|
||||
boolean currentCbId = this.getIdProperty().isAnnotationPresent(Id.class);
|
||||
boolean currentSpringId = this.getIdProperty().isAnnotationPresent(org.springframework.data.annotation.Id.class);
|
||||
boolean candidateCbId = property.isAnnotationPresent(Id.class);
|
||||
boolean candidateSpringId = property.isAnnotationPresent(org.springframework.data.annotation.Id.class);
|
||||
|
||||
if (currentCbId && candidateSpringId) {
|
||||
//spring IDs will have priority over SDK IDs
|
||||
return property;
|
||||
} else if (currentSpringId && candidateCbId) {
|
||||
//ignore SDK's IDs if current is a Spring ID
|
||||
return null;
|
||||
}
|
||||
/* any of the following will throw:
|
||||
- current is a spring ID and the candidate bears another spring ID
|
||||
- current is a SDK ID and the candidate bears another SDK ID
|
||||
- any other combination involving something else than a SDK or Spring ID
|
||||
*/
|
||||
return super.returnPropertyIfBetterIdPropertyCandidateOrNull(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expiration time of the entity.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.springframework.data.couchbase.core.mapping;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Id;
|
||||
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
@@ -28,7 +30,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Implements annotated property representations of a given Field instance.
|
||||
* Implements annotated property representations of a given {@link com.couchbase.client.java.repository.annotation.Field} instance.
|
||||
* <p/>
|
||||
* <p>This object is used to gather information out of properties on objects that need to be persisted. For example, it
|
||||
* supports overriding of the actual property name by providing custom annotations.</p>
|
||||
@@ -73,8 +75,8 @@ public class BasicCouchbasePersistentProperty
|
||||
*/
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
org.springframework.data.couchbase.core.mapping.Field annotation = getField().
|
||||
getAnnotation(org.springframework.data.couchbase.core.mapping.Field.class);
|
||||
com.couchbase.client.java.repository.annotation.Field annotation = getField().
|
||||
getAnnotation(com.couchbase.client.java.repository.annotation.Field.class);
|
||||
|
||||
if (annotation != null && StringUtils.hasText(annotation.value())) {
|
||||
return annotation.value();
|
||||
@@ -90,4 +92,9 @@ public class BasicCouchbasePersistentProperty
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
// DATACOUCH-145: allows SDK's @Id annotation to be used
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return isAnnotationPresent(Id.class) || super.isIdProperty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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
|
||||
*
|
||||
* http://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.couchbase.core.mapping;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Annotation to define custom metadata for document fields.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Field {
|
||||
|
||||
/**
|
||||
* The key to be used to store the field inside the document.
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.data.couchbase.core;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.couchbase.core.mapping.Field;
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.data.couchbase.core.mapping;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@@ -24,6 +26,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
@@ -68,6 +71,55 @@ public class BasicCouchbasePersistentPropertyTests {
|
||||
assertEquals("foobar", getPropertyFor(field).getFieldName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrefersSpringIdAnnotation() {
|
||||
BasicCouchbasePersistentEntity<Beer> test = new BasicCouchbasePersistentEntity<Beer>(
|
||||
ClassTypeInformation.from(Beer.class));
|
||||
|
||||
Field sdkIdField = ReflectionUtils.findField(Beer.class, "sdkId");
|
||||
CouchbasePersistentProperty sdkIdProperty = getPropertyFor(sdkIdField);
|
||||
Field springIdField = ReflectionUtils.findField(Beer.class, "springId");
|
||||
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
|
||||
test.addPersistentProperty(sdkIdProperty);
|
||||
test.addPersistentProperty(springIdProperty);
|
||||
|
||||
assertEquals("sdkId", sdkIdProperty.getFieldName());
|
||||
assertEquals("springId", springIdProperty.getFieldName());
|
||||
|
||||
assertTrue(sdkIdProperty.isIdProperty());
|
||||
assertTrue(springIdProperty.isIdProperty());
|
||||
|
||||
assertEquals(springIdProperty, test.getIdProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsSdkIdAnnotation() {
|
||||
BasicCouchbasePersistentEntity<SdkIdentified> test = new BasicCouchbasePersistentEntity<SdkIdentified>(
|
||||
ClassTypeInformation.from(SdkIdentified.class));
|
||||
Field id = ReflectionUtils.findField(SdkIdentified.class, "id");
|
||||
CouchbasePersistentProperty idProperty = getPropertyFor(id);
|
||||
test.addPersistentProperty(idProperty);
|
||||
|
||||
assertEquals(idProperty, test.getIdProperty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSdkIdAnnotationEvaluatedAfterSpringIdAnnotationIsIgnored() {
|
||||
BasicCouchbasePersistentEntity<Beer> test = new BasicCouchbasePersistentEntity<Beer>(
|
||||
ClassTypeInformation.from(Beer.class));
|
||||
Field sdkIdField = ReflectionUtils.findField(Beer.class, "sdkId");
|
||||
CouchbasePersistentProperty sdkIdProperty = getPropertyFor(sdkIdField);
|
||||
Field springIdField = ReflectionUtils.findField(Beer.class, "springId");
|
||||
CouchbasePersistentProperty springIdProperty = getPropertyFor(springIdField);
|
||||
|
||||
//here this simulates the order in which the annotations would be found
|
||||
// when "overriding" Spring @Id with SDK's @Id...
|
||||
test.addPersistentProperty(springIdProperty);
|
||||
assertEquals(springIdProperty, test.getIdProperty());
|
||||
test.addPersistentProperty(sdkIdProperty);
|
||||
assertEquals(springIdProperty, test.getIdProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a property out of the field.
|
||||
*
|
||||
@@ -84,16 +136,29 @@ public class BasicCouchbasePersistentPropertyTests {
|
||||
*/
|
||||
public class Beer {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
@com.couchbase.client.java.repository.annotation.Id
|
||||
private String sdkId;
|
||||
|
||||
@org.springframework.data.couchbase.core.mapping.Field("foobar")
|
||||
@Id
|
||||
private String springId;
|
||||
|
||||
@com.couchbase.client.java.repository.annotation.Field("foobar")
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
return springId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple POJO to test that a single ID property from the SDK is taken into account.
|
||||
*/
|
||||
public class SdkIdentified {
|
||||
@com.couchbase.client.java.repository.annotation.Id
|
||||
private String id;
|
||||
|
||||
String value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,13 +25,14 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.repository.annotation.Field;
|
||||
import com.couchbase.client.java.repository.annotation.Id;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.couchbase.UnitTestApplicationConfig;
|
||||
@@ -121,7 +122,7 @@ public class CustomConvertersTests {
|
||||
}
|
||||
|
||||
public static class BlogPost {
|
||||
@Id
|
||||
@Id //also tests DATACOUCH-145 (this is SDK's @Id)
|
||||
public String id = "key";
|
||||
|
||||
@Field
|
||||
|
||||
@@ -483,6 +483,25 @@ public class MappingCouchbaseConverterTests {
|
||||
assertEquals(deleted.toDate().getTime(), read.deleted.toDate().getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPrioritizeSpringIdOverSdkId() {
|
||||
SpringIdentified entity = new SpringIdentified();
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
converter.write(entity, converted);
|
||||
|
||||
assertEquals("realId", converted.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldIgnoreSdkIdIfSpringIdFound() {
|
||||
AmbiguousIdentified entity = new AmbiguousIdentified();
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
converter.write(entity, converted);
|
||||
|
||||
assertEquals("springId", converted.getId());
|
||||
}
|
||||
|
||||
|
||||
static class EntityWithoutID {
|
||||
private String attr0;
|
||||
|
||||
@@ -661,4 +680,21 @@ public class MappingCouchbaseConverterTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class SdkIdentified {
|
||||
@com.couchbase.client.java.repository.annotation.Id
|
||||
public String id = "id";
|
||||
}
|
||||
|
||||
static class SpringIdentified extends SdkIdentified {
|
||||
@org.springframework.data.annotation.Id
|
||||
public String realId = "realId";
|
||||
}
|
||||
|
||||
static class AmbiguousIdentified {
|
||||
@org.springframework.data.annotation.Id
|
||||
public String springId = "springId";
|
||||
|
||||
@com.couchbase.client.java.repository.annotation.Id
|
||||
public String sdkId = "sdkId";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user