Added support of the PersistTo, ReplicateTo and DurabilityLevel via @Document annotation. (#1649)
Closes #1486
This commit is contained in:
committed by
mikereiche
parent
09257b791a
commit
021e11a4ab
@@ -77,6 +77,9 @@ The `@Id` annotation needs to be present because every document in Couchbase nee
|
||||
This key needs to be any string with a length of maximum 250 characters.
|
||||
Feel free to use whatever fits your use case, be it a UUID, an email address or anything else.
|
||||
|
||||
Writes to Couchbase-Server buckets can optionally be assigned durability requirements; which instruct Couchbase Server to update the specified document on multiple nodes in memory and/or disk locations across the cluster; before considering the write to be committed.
|
||||
Default durability requirements can also be configured through the `@Document` annotation.
|
||||
For example: `@Document(durabilityLevel = DurabilityLevel.MAJORITY)` will force mutations to be replicated to a majority of the Data Service nodes.
|
||||
[[datatypes]]
|
||||
== Datatypes and Converters
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ User found = couchbaseTemplate.findById(User.class).one(user.getId());
|
||||
----
|
||||
====
|
||||
|
||||
If you wanted to use a custom durability requirement for the `upsert` operation you can chain it in:
|
||||
If you wanted to use a custom (by default durability options from the `@Document` annotation will be used) durability requirement for the `upsert` operation you can chain it in:
|
||||
|
||||
.Upsert with durability
|
||||
====
|
||||
|
||||
@@ -39,7 +39,8 @@ public class ExecutableInsertByIdOperationSupport implements ExecutableInsertByI
|
||||
public <T> ExecutableInsertById<T> insertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
* {@link ExecutableRemoveByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByIdOperation {
|
||||
|
||||
@@ -50,7 +51,8 @@ public class ExecutableRemoveByIdOperationSupport implements ExecutableRemoveByI
|
||||
public ExecutableRemoveById removeById(Class<?> domainType) {
|
||||
|
||||
return new ExecutableRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ public class ExecutableReplaceByIdOperationSupport implements ExecutableReplaceB
|
||||
public <T> ExecutableReplaceById<T> replaceById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ public class ExecutableUpsertByIdOperationSupport implements ExecutableUpsertByI
|
||||
public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ExecutableUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
* {@link ReactiveInsertByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOperation {
|
||||
|
||||
@@ -59,7 +60,8 @@ public class ReactiveInsertByIdOperationSupport implements ReactiveInsertByIdOpe
|
||||
public <T> ReactiveInsertById<T> insertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveInsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
* {@link ReactiveRemoveByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOperation {
|
||||
|
||||
@@ -65,7 +66,8 @@ public class ReactiveRemoveByIdOperationSupport implements ReactiveRemoveByIdOpe
|
||||
@Override
|
||||
public ReactiveRemoveById removeById(Class<?> domainType) {
|
||||
return new ReactiveRemoveByIdSupport(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
* {@link ReactiveReplaceByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdOperation {
|
||||
|
||||
@@ -62,7 +63,8 @@ public class ReactiveReplaceByIdOperationSupport implements ReactiveReplaceByIdO
|
||||
public <T> ReactiveReplaceById<T> replaceById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveReplaceByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.couchbase.client.java.kv.UpsertOptions;
|
||||
* {@link ReactiveUpsertByIdOperation} implementations for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOperation {
|
||||
|
||||
@@ -51,7 +52,8 @@ public class ReactiveUpsertByIdOperationSupport implements ReactiveUpsertByIdOpe
|
||||
public <T> ReactiveUpsertById<T> upsertById(final Class<T> domainType) {
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
return new ReactiveUpsertByIdSupport<>(template, domainType, OptionsBuilder.getScopeFrom(domainType),
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, PersistTo.NONE, ReplicateTo.NONE, DurabilityLevel.NONE,
|
||||
OptionsBuilder.getCollectionFrom(domainType), null, OptionsBuilder.getPersistTo(domainType),
|
||||
OptionsBuilder.getReplicateTo(domainType), OptionsBuilder.getDurabilityLevel(domainType),
|
||||
null, template.support());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
@@ -36,6 +39,7 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Andrey Rubtsov
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@Persistent
|
||||
@Inherited
|
||||
@@ -84,4 +88,22 @@ public @interface Document {
|
||||
*/
|
||||
@AliasFor(annotation = ScanConsistency.class, attribute = "query")
|
||||
QueryScanConsistency queryScanConsistency() default QueryScanConsistency.NOT_BOUNDED;
|
||||
|
||||
/**
|
||||
* How many persisted copies of the modified record must exist on the given document. Default is {@link PersistTo#NONE}.
|
||||
* For Couchbase version >= 6.5 see {@link #durabilityLevel()}.
|
||||
*/
|
||||
PersistTo persistTo() default PersistTo.NONE;
|
||||
|
||||
/**
|
||||
* How many replicas must this documents operations be propagated to. Default is {@link ReplicateTo#NONE}.
|
||||
* For Couchbase version >= 6.5 see {@link #durabilityLevel()}.
|
||||
*/
|
||||
ReplicateTo replicateTo() default ReplicateTo.NONE;
|
||||
|
||||
/**
|
||||
* The optional durabilityLevel for all mutating operations, allows the application to wait until this replication
|
||||
* (or persistence) is successful before proceeding
|
||||
*/
|
||||
DurabilityLevel durabilityLevel() default DurabilityLevel.NONE;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.mapping.Expiry;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
@@ -56,6 +58,7 @@ import com.couchbase.client.java.transactions.TransactionQueryOptions;
|
||||
* Methods for building Options objects for Couchbae APIs.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
public class OptionsBuilder {
|
||||
|
||||
@@ -225,6 +228,30 @@ public class OptionsBuilder {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DurabilityLevel getDurabilityLevel(Class<?> domainType) {
|
||||
if (domainType == null) {
|
||||
return DurabilityLevel.NONE;
|
||||
}
|
||||
Document document = AnnotatedElementUtils.findMergedAnnotation(domainType, Document.class);
|
||||
return document != null ? document.durabilityLevel() : DurabilityLevel.NONE;
|
||||
}
|
||||
|
||||
public static PersistTo getPersistTo(Class<?> domainType) {
|
||||
if (domainType == null) {
|
||||
return PersistTo.NONE;
|
||||
}
|
||||
Document document = AnnotatedElementUtils.findMergedAnnotation(domainType, Document.class);
|
||||
return document != null ? document.persistTo() : PersistTo.NONE;
|
||||
}
|
||||
|
||||
public static ReplicateTo getReplicateTo(Class<?> domainType) {
|
||||
if (domainType == null) {
|
||||
return ReplicateTo.NONE;
|
||||
}
|
||||
Document document = AnnotatedElementUtils.findMergedAnnotation(domainType, Document.class);
|
||||
return document != null ? document.replicateTo() : ReplicateTo.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* collection annotation
|
||||
|
||||
@@ -43,21 +43,8 @@ import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.couchbase.core.ExecutableFindByIdOperation.ExecutableFindById;
|
||||
import org.springframework.data.couchbase.core.ExecutableRemoveByIdOperation.ExecutableRemoveById;
|
||||
import org.springframework.data.couchbase.core.ExecutableReplaceByIdOperation.ExecutableReplaceById;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllEntity;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllId;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.PersonValue;
|
||||
import org.springframework.data.couchbase.domain.Submission;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated2;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated3;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotatedTouchOnRead;
|
||||
import org.springframework.data.couchbase.domain.UserSubmission;
|
||||
import org.springframework.data.couchbase.core.support.*;
|
||||
import org.springframework.data.couchbase.domain.*;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.couchbase.util.JavaIntegrationTests;
|
||||
@@ -74,6 +61,7 @@ import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@@ -275,49 +263,58 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
|
||||
@Test
|
||||
void withDurability()
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
|
||||
// insert, replace, upsert
|
||||
for (OneAndAllEntity<User> operator : new OneAndAllEntity[] { couchbaseTemplate.insertById(clazz),
|
||||
couchbaseTemplate.replaceById(clazz), couchbaseTemplate.upsertById(clazz) }) {
|
||||
// create an entity of type clazz
|
||||
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
|
||||
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
|
||||
"firstname", "lastname");
|
||||
for (Class<?> clazz : new Class[] { User.class, UserAnnotatedDurability.class, UserAnnotatedPersistTo.class, UserAnnotatedReplicateTo.class }) {
|
||||
// insert, replace, upsert
|
||||
for (OneAndAllEntity<User> operator : new OneAndAllEntity[]{couchbaseTemplate.insertById(clazz),
|
||||
couchbaseTemplate.replaceById(clazz), couchbaseTemplate.upsertById(clazz)}) {
|
||||
// create an entity of type clazz
|
||||
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
|
||||
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
|
||||
"firstname", "lastname");
|
||||
|
||||
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
|
||||
operator = (OneAndAllEntity) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
|
||||
ReplicateTo.NONE);
|
||||
}
|
||||
|
||||
// if replace, we need to insert a document to replace
|
||||
if (operator instanceof ExecutableReplaceById) {
|
||||
couchbaseTemplate.insertById(User.class).one(user);
|
||||
}
|
||||
// call to insert/replace/update
|
||||
User returned = null;
|
||||
|
||||
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
|
||||
for (int i = 1; i != 5; i++) {
|
||||
try {
|
||||
returned = (User) operator.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
}
|
||||
sleepSecs(1);
|
||||
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
|
||||
operator = (OneAndAllEntity<User>) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
|
||||
ReplicateTo.NONE);
|
||||
} else if (clazz.equals(UserAnnotatedReplicateTo.class)){ // override the replica count from the annotation with no replica
|
||||
operator = (OneAndAllEntity<User>) ((WithDurability<User>) operator).withDurability(PersistTo.NONE,
|
||||
ReplicateTo.NONE);
|
||||
}
|
||||
}
|
||||
assertEquals(user, returned);
|
||||
User found = couchbaseTemplate.findById(User.class).one(user.getId());
|
||||
assertEquals(user, found);
|
||||
|
||||
if (operator instanceof ExecutableReplaceById) {
|
||||
couchbaseTemplate.removeById().withDurability(PersistTo.ACTIVE, ReplicateTo.NONE).one(user.getId());
|
||||
User removed = (User) couchbaseTemplate.findById(user.getClass()).one(user.getId());
|
||||
assertNull(removed, "found should have been null as document should be removed");
|
||||
// if replace, we need to insert a document to replace
|
||||
if (operator instanceof ExecutableReplaceById) {
|
||||
couchbaseTemplate.insertById(User.class).one(user);
|
||||
}
|
||||
// call to insert/replace/update
|
||||
User returned = null;
|
||||
|
||||
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
|
||||
for (int i = 1; i != 5; i++) {
|
||||
try {
|
||||
returned = (User) operator.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
}
|
||||
sleepSecs(1);
|
||||
}
|
||||
}
|
||||
assertEquals(user, returned);
|
||||
User found = couchbaseTemplate.findById(User.class).one(user.getId());
|
||||
assertEquals(user, found);
|
||||
|
||||
if (operator instanceof ExecutableReplaceById) {
|
||||
if (clazz.equals(UserAnnotatedReplicateTo.class)){ // override the replica count from the annotation with no replica
|
||||
couchbaseTemplate.removeById(clazz).withDurability(PersistTo.ACTIVE,
|
||||
ReplicateTo.NONE).one(user.getId());
|
||||
} else {
|
||||
couchbaseTemplate.removeById(clazz).one(user.getId());
|
||||
}
|
||||
User removed = (User) couchbaseTemplate.findById(user.getClass()).one(user.getId());
|
||||
assertNull(removed, "found should have been null as document should be removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,6 +471,56 @@ class CouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationTests {
|
||||
couchbaseTemplate.removeById(User.class).one(user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertByIdWithAnnotatedDurability() {
|
||||
UserAnnotatedPersistTo user = new UserAnnotatedPersistTo(UUID.randomUUID().toString(), "firstname", "lastname");
|
||||
UserAnnotatedPersistTo inserted = null;
|
||||
|
||||
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
|
||||
for (int i = 1; i != 5; i++) {
|
||||
try {
|
||||
inserted = couchbaseTemplate.insertById(UserAnnotatedPersistTo.class)
|
||||
.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(UserAnnotatedPersistTo.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
}
|
||||
sleepSecs(1);
|
||||
}
|
||||
}
|
||||
assertEquals(user, inserted);
|
||||
assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(UserAnnotatedPersistTo.class).one(user));
|
||||
couchbaseTemplate.removeById(UserAnnotatedPersistTo.class).one(user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertByIdWithAnnotatedDurability2() {
|
||||
UserAnnotatedDurability user = new UserAnnotatedDurability(UUID.randomUUID().toString(), "firstname", "lastname");
|
||||
UserAnnotatedDurability inserted = null;
|
||||
|
||||
// occasionally gives "reactor.core.Exceptions$OverflowException: Could not emit value due to lack of requests"
|
||||
for (int i = 1; i != 5; i++) {
|
||||
try {
|
||||
inserted = couchbaseTemplate.insertById(UserAnnotatedDurability.class)
|
||||
.one(user);
|
||||
break;
|
||||
} catch (Exception ofe) {
|
||||
System.out.println("" + i + " caught: " + ofe);
|
||||
couchbaseTemplate.removeByQuery(UserAnnotatedDurability.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
|
||||
if (i == 4) {
|
||||
throw ofe;
|
||||
}
|
||||
sleepSecs(1);
|
||||
}
|
||||
}
|
||||
assertEquals(user, inserted);
|
||||
assertThrows(DuplicateKeyException.class, () -> couchbaseTemplate.insertById(UserAnnotatedDurability.class).one(user));
|
||||
couchbaseTemplate.removeById(UserAnnotatedDurability.class).one(user.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsById() {
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
||||
@@ -48,13 +48,7 @@ import org.springframework.data.couchbase.core.support.OneAndAllEntityReactive;
|
||||
import org.springframework.data.couchbase.core.support.OneAndAllIdReactive;
|
||||
import org.springframework.data.couchbase.core.support.WithDurability;
|
||||
import org.springframework.data.couchbase.core.support.WithExpiry;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.PersonValue;
|
||||
import org.springframework.data.couchbase.domain.ReactiveNaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated2;
|
||||
import org.springframework.data.couchbase.domain.UserAnnotated3;
|
||||
import org.springframework.data.couchbase.domain.*;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.couchbase.util.JavaIntegrationTests;
|
||||
@@ -68,6 +62,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
@SpringJUnitConfig(Config.class)
|
||||
@@ -158,36 +153,44 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
|
||||
@Test
|
||||
void withDurability()
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
Class<?> clazz = User.class; // for now, just User.class. There is no Durability annotation.
|
||||
// insert, replace, upsert
|
||||
for (OneAndAllEntityReactive<User> operator : new OneAndAllEntityReactive[] {
|
||||
reactiveCouchbaseTemplate.insertById(clazz), reactiveCouchbaseTemplate.replaceById(clazz),
|
||||
reactiveCouchbaseTemplate.upsertById(clazz) }) {
|
||||
// create an entity of type clazz
|
||||
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
|
||||
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
|
||||
"firstname", "lastname");
|
||||
for (Class<?> clazz : new Class[] { User.class, UserAnnotatedDurability.class, UserAnnotatedPersistTo.class, UserAnnotatedReplicateTo.class }) {
|
||||
// insert, replace, upsert
|
||||
for (OneAndAllEntityReactive<User> operator : new OneAndAllEntityReactive[]{
|
||||
reactiveCouchbaseTemplate.insertById(clazz), reactiveCouchbaseTemplate.replaceById(clazz),
|
||||
reactiveCouchbaseTemplate.upsertById(clazz)}) {
|
||||
// create an entity of type clazz
|
||||
Constructor<?> cons = clazz.getConstructor(String.class, String.class, String.class);
|
||||
User user = (User) cons.newInstance("" + operator.getClass().getSimpleName() + "_" + clazz.getSimpleName(),
|
||||
"firstname", "lastname");
|
||||
|
||||
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
|
||||
operator = (OneAndAllEntityReactive<User>) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
|
||||
ReplicateTo.NONE);
|
||||
}
|
||||
if (clazz.equals(User.class)) { // User.java doesn't have an durability annotation
|
||||
operator = (OneAndAllEntityReactive<User>) ((WithDurability<User>) operator).withDurability(PersistTo.ACTIVE,
|
||||
ReplicateTo.NONE);
|
||||
} else if (clazz.equals(UserAnnotatedReplicateTo.class)){ // override the replica count from the annotation with no replica
|
||||
operator = (OneAndAllEntityReactive<User>) ((WithDurability<User>) operator).withDurability(PersistTo.NONE,
|
||||
ReplicateTo.NONE);
|
||||
}
|
||||
|
||||
// if replace, we need to insert a document to replace
|
||||
if (operator instanceof ReactiveReplaceById) {
|
||||
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
|
||||
}
|
||||
// call to insert/replace/update
|
||||
User returned = operator.one(user).block();
|
||||
assertEquals(user, returned);
|
||||
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
|
||||
assertEquals(user, found);
|
||||
// if replace, we need to insert a document to replace
|
||||
if (operator instanceof ReactiveReplaceById) {
|
||||
reactiveCouchbaseTemplate.insertById(User.class).one(user).block();
|
||||
}
|
||||
// call to insert/replace/update
|
||||
User returned = operator.one(user).block();
|
||||
assertEquals(user, returned);
|
||||
User found = reactiveCouchbaseTemplate.findById(User.class).one(user.getId()).block();
|
||||
assertEquals(user, found);
|
||||
|
||||
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById) {
|
||||
reactiveCouchbaseTemplate.removeById().withDurability(PersistTo.ACTIVE, ReplicateTo.NONE).one(user.getId())
|
||||
.block();
|
||||
User removed = (User) reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
|
||||
assertNull(removed, "found should have been null as document should be removed");
|
||||
if (operator instanceof ReactiveReplaceByIdOperation.ReactiveReplaceById) {
|
||||
if (clazz.equals(UserAnnotatedReplicateTo.class)){ // override the replica count from the annotation with no replica
|
||||
reactiveCouchbaseTemplate.removeById(clazz).withDurability(PersistTo.NONE,
|
||||
ReplicateTo.NONE).one(user.getId()).block();
|
||||
} else {
|
||||
reactiveCouchbaseTemplate.removeById(clazz).one(user.getId()).block();
|
||||
}
|
||||
User removed = reactiveCouchbaseTemplate.findById(user.getClass()).one(user.getId()).block();
|
||||
assertNull(removed, "found should have been null as document should be removed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +333,24 @@ class ReactiveCouchbaseTemplateKeyValueIntegrationTests extends JavaIntegrationT
|
||||
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertByIdWithAnnotatedDurability() {
|
||||
UserAnnotatedPersistTo user = new UserAnnotatedPersistTo(UUID.randomUUID().toString(), "firstname", "lastname");
|
||||
UserAnnotatedPersistTo inserted = reactiveCouchbaseTemplate.insertById(UserAnnotatedPersistTo.class)
|
||||
.one(user).block();
|
||||
assertEquals(user, inserted);
|
||||
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertByIdWithAnnotatedDurability2() {
|
||||
UserAnnotatedDurability user = new UserAnnotatedDurability(UUID.randomUUID().toString(), "firstname", "lastname");
|
||||
UserAnnotatedDurability inserted = reactiveCouchbaseTemplate.insertById(UserAnnotatedDurability.class)
|
||||
.one(user).block();
|
||||
assertEquals(user, inserted);
|
||||
assertThrows(DuplicateKeyException.class, () -> reactiveCouchbaseTemplate.insertById(User.class).one(user).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsById() {
|
||||
String id = UUID.randomUUID().toString();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Person entity for tests.
|
||||
*
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@Document(persistTo = PersistTo.ONE, replicateTo = ReplicateTo.ONE)
|
||||
public class PersonWithDurability extends Person {
|
||||
public PersonWithDurability() {
|
||||
setId(UUID.randomUUID());
|
||||
setMiddlename("Nick");
|
||||
}
|
||||
|
||||
public PersonWithDurability(String firstname, String lastname) {
|
||||
this();
|
||||
setFirstname(firstname);
|
||||
setLastname(lastname);
|
||||
isNew(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Person entity for tests.
|
||||
*
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@Document(durabilityLevel = DurabilityLevel.MAJORITY)
|
||||
public class PersonWithDurability2 extends Person {
|
||||
public PersonWithDurability2() {
|
||||
setId(UUID.randomUUID());
|
||||
setMiddlename("Nick");
|
||||
}
|
||||
|
||||
public PersonWithDurability2(String firstname, String lastname) {
|
||||
this();
|
||||
setFirstname(firstname);
|
||||
setLastname(lastname);
|
||||
isNew(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020-2023 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.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Annotated User entity for tests
|
||||
*
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
|
||||
@Document(durabilityLevel = DurabilityLevel.MAJORITY)
|
||||
public class UserAnnotatedDurability extends User implements Serializable {
|
||||
|
||||
public UserAnnotatedDurability(String id, String firstname, String lastname) {
|
||||
super(id, firstname, lastname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020-2023 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.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.java.kv.PersistTo;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Annotated User entity for tests
|
||||
*
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
|
||||
@Document(persistTo = PersistTo.ACTIVE)
|
||||
public class UserAnnotatedPersistTo extends User implements Serializable {
|
||||
|
||||
public UserAnnotatedPersistTo(String id, String firstname, String lastname) {
|
||||
super(id, firstname, lastname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020-2023 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.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.core.msg.kv.DurabilityLevel;
|
||||
import com.couchbase.client.java.kv.ReplicateTo;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Annotated User entity for tests
|
||||
*
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
|
||||
@Document(replicateTo = ReplicateTo.ONE)
|
||||
public class UserAnnotatedReplicateTo extends User implements Serializable {
|
||||
|
||||
public UserAnnotatedReplicateTo(String id, String firstname, String lastname) {
|
||||
super(id, firstname, lastname);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.domain.Person;
|
||||
import org.springframework.data.couchbase.domain.PersonWithDurability;
|
||||
import org.springframework.data.couchbase.domain.PersonWithDurability2;
|
||||
import org.springframework.data.couchbase.transaction.error.TransactionSystemUnambiguousException;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
@@ -54,6 +56,7 @@ import com.couchbase.client.java.kv.ReplicateTo;
|
||||
*
|
||||
* @author Graham Pople
|
||||
* @author Michael Reiche
|
||||
* @author Tigran Babloyan
|
||||
*/
|
||||
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
|
||||
@SpringJUnitConfig(classes = { TransactionsConfig.class,
|
||||
@@ -96,6 +99,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using insertById() with Durability set via annotations in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void insertWithDurabilityAnnotated() {
|
||||
test((ops) -> {
|
||||
ops.insertById(PersonWithDurability.class).one(new PersonWithDurability("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using insertById().withExpiry in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void insertWithExpiry() {
|
||||
@@ -112,6 +123,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using insertById with Durability set via annotations in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void insertWithDurabilityAnnotated2() {
|
||||
test((ops) -> {
|
||||
ops.insertById(PersonWithDurability2.class).one(new PersonWithDurability2("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using insertById().withOptions in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void insertWithOptions() {
|
||||
@@ -128,6 +147,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using replaceById() with Durability set via annotations in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void replaceWithAnnotatedDurability() {
|
||||
test((ops) -> {
|
||||
ops.replaceById(PersonWithDurability.class).one(new PersonWithDurability("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using replaceById().withExpiry in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void replaceWithExpiry() {
|
||||
@@ -144,6 +171,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using replaceById() with Durability set via annotations in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void replaceWithAnnotatedDurability2() {
|
||||
test((ops) -> {
|
||||
ops.replaceById(PersonWithDurability2.class).one(new PersonWithDurability2("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using replaceById().withOptions in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void replaceWithOptions() {
|
||||
@@ -160,6 +195,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using removeById() with Durability set via annotations in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void removeWithAnnotatedDurability() {
|
||||
test((ops) -> {
|
||||
ops.removeById(PersonWithDurability.class).oneEntity(new PersonWithDurability("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using removeById().withDurability(durabilityLevel) in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void removeWithDurability2() {
|
||||
@@ -168,6 +211,14 @@ public class CouchbaseTransactionalUnsettableParametersIntegrationTests extends
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using removeById().withDurability(durabilityLevel) in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void removeWithAnnotatedDurability2() {
|
||||
test((ops) -> {
|
||||
ops.removeById(PersonWithDurability2.class).oneEntity(new PersonWithDurability2("Walter", "White"));
|
||||
});
|
||||
}
|
||||
|
||||
@DisplayName("Using removeById().withOptions in a transaction is rejected at runtime")
|
||||
@Test
|
||||
public void removeWithOptions() {
|
||||
|
||||
Reference in New Issue
Block a user