Added support of the PersistTo, ReplicateTo and DurabilityLevel via @Document annotation. (#1649)

Closes #1486
This commit is contained in:
Tigran Babloyan
2023-01-25 01:05:52 +04:00
committed by mikereiche
parent 09257b791a
commit 021e11a4ab
20 changed files with 477 additions and 98 deletions

View File

@@ -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

View File

@@ -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
====

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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);
}

View File

@@ -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());
}

View File

@@ -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());
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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() {