Skip to content

Commit a94865d

Browse files
babltigamikereiche
authored andcommitted
Added Sub-Document mutations support (#1660)
Closes #1659
1 parent 1a97c73 commit a94865d

16 files changed

+2168
-18
lines changed

src/main/asciidoc/template.adoc

+75
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,79 @@ final List<User> foundUsers = couchbaseTemplate
5454
.consistentWith(QueryScanConsistency.REQUEST_PLUS)
5555
.all();
5656
----
57+
====
58+
59+
60+
[[template.sub-document-ops]]
61+
== Sub-Document Operations
62+
63+
Couchbase supports https://docs.couchbase.com/java-sdk/current/howtos/subdocument-operations.html[Sub-Document Operations]. This section documents how to use it with Spring Data Couchbase.
64+
65+
66+
67+
Sub-Document operations may be quicker and more network-efficient than full-document operations such as upsert or replace because they only transmit the accessed sections of the document over the network.
68+
69+
Sub-Document operations are also atomic, in that if one Sub-Document mutation fails then all will, allowing safe modifications to documents with built-in concurrency control.
70+
71+
Currently Spring Data Couchbase supports only sub document mutations (remove, upsert, replace and insert).
72+
73+
Mutation operations modify one or more paths in the document. The simplest of these operations is upsert, which, similar to the fulldoc-level upsert, will either modify the value of an existing path or create it if it does not exist:
74+
75+
Following example will upsert the city field on the address of the user, without trasfering any additional user document data.
76+
77+
.MutateIn upsert on the template
78+
====
79+
[source,java]
80+
----
81+
User user = new User();
82+
// id field on the base document id required
83+
user.setId(ID);
84+
user.setAddress(address);
85+
couchbaseTemplate.mutateInById(User.class)
86+
.withUpsertPaths("address.city")
87+
.one(user);
88+
----
89+
====
90+
91+
[[template.sub-document-ops]]
92+
=== Executing Multiple Sub-Document Operations
93+
94+
Multiple Sub-Document operations can be executed at once on the same document, allowing you to modify several Sub-Documents at once. When multiple operations are submitted within the context of a single mutateIn command, the server will execute all the operations with the same version of the document.
95+
96+
To execute several mutation operations the method chaining can be used.
97+
98+
.MutateIn Multiple Operations
99+
====
100+
[source,java]
101+
----
102+
couchbaseTemplate.mutateInById(User.class)
103+
.withInsertPaths("roles", "subuser.firstname")
104+
.withRemovePaths("address.city")
105+
.withUpsertPaths("firstname")
106+
.withReplacePaths("address.street")
107+
.one(user);
108+
----
109+
====
110+
111+
[[template.sub-document-cas]]
112+
=== Concurrent Modifications
113+
114+
Concurrent Sub-Document operations on different parts of a document will not conflict so by default the CAS value will be not be supplied when executing the mutations.
115+
If CAS is required then it can be provided like this:
116+
117+
.MutateIn With CAS
118+
====
119+
[source,java]
120+
----
121+
User user = new User();
122+
// id field on the base document id required
123+
user.setId(ID);
124+
// @Version field should have a value for CAS to be supplied
125+
user.setVersion(cas);
126+
user.setAddress(address);
127+
couchbaseTemplate.mutateInById(User.class)
128+
.withUpsertPaths("address.city")
129+
.withCasProvided()
130+
.one(user);
131+
----
57132
====

src/main/java/org/springframework/data/couchbase/core/CouchbaseExceptionTranslator.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ConcurrentModificationException;
2020
import java.util.concurrent.TimeoutException;
2121

22+
import com.couchbase.client.core.error.subdoc.*;
2223
import org.springframework.dao.DataAccessException;
2324
import org.springframework.dao.DataAccessResourceFailureException;
2425
import org.springframework.dao.DataIntegrityViolationException;
@@ -63,6 +64,7 @@
6364
* @author Simon Baslé
6465
* @author Michael Reiche
6566
* @author Graham Pople
67+
* @author Tigran Babloyan
6668
*/
6769
public class CouchbaseExceptionTranslator implements PersistenceExceptionTranslator {
6870

@@ -102,7 +104,11 @@ public final DataAccessException translateExceptionIfPossible(final RuntimeExcep
102104
return new OperationCancellationException(ex.getMessage(), ex);
103105
}
104106

105-
if (ex instanceof DesignDocumentNotFoundException || ex instanceof ValueTooLargeException) {
107+
if (ex instanceof DesignDocumentNotFoundException || ex instanceof ValueTooLargeException
108+
|| ex instanceof PathExistsException || ex instanceof PathInvalidException
109+
|| ex instanceof PathNotFoundException || ex instanceof PathMismatchException
110+
|| ex instanceof PathTooDeepException || ex instanceof ValueInvalidException
111+
|| ex instanceof ValueTooDeepException || ex instanceof DocumentTooDeepException) {
106112
return new InvalidDataAccessResourceUsageException(ex.getMessage(), ex);
107113
}
108114

src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* @author Michael Nitschinger
4242
* @author Michael Reiche
4343
* @author Jorge Rodriguez Martin
44+
* @author Tigran Babloyan
4445
* @since 3.0
4546
*/
4647
public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContextAware {
@@ -95,6 +96,11 @@ public <T> ExecutableUpsertById<T> upsertById(final Class<T> domainType) {
9596
return new ExecutableUpsertByIdOperationSupport(this).upsertById(domainType);
9697
}
9798

99+
@Override
100+
public <T> ExecutableMutateInById<T> mutateInById(Class<T> domainType) {
101+
return new ExecutableMutateInByIdOperationSupport(this).mutateInById(domainType);
102+
}
103+
98104
@Override
99105
public <T> ExecutableInsertById<T> insertById(Class<T> domainType) {
100106
return new ExecutableInsertByIdOperationSupport(this).insertById(domainType);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.couchbase.core;
17+
18+
import com.couchbase.client.core.msg.kv.DurabilityLevel;
19+
import com.couchbase.client.java.kv.MutateInOptions;
20+
import com.couchbase.client.java.kv.PersistTo;
21+
import com.couchbase.client.java.kv.ReplicateTo;
22+
import org.springframework.data.couchbase.core.support.*;
23+
import reactor.core.publisher.Flux;
24+
import reactor.core.publisher.Mono;
25+
26+
import java.time.Duration;
27+
import java.util.Collection;
28+
29+
/**
30+
* Mutate In Operations
31+
*
32+
* @author Tigran Babloyan
33+
* @since 5.1
34+
*/
35+
public interface ExecutableMutateInByIdOperation {
36+
37+
/**
38+
* Mutate using the KV service.
39+
*
40+
* @param domainType the entity type to mutate.
41+
*/
42+
<T> ExecutableMutateInById<T> mutateInById(Class<T> domainType);
43+
44+
/**
45+
* Terminating operations invoking the actual execution.
46+
*/
47+
interface TerminatingMutateInById<T> extends OneAndAllEntity<T> {
48+
49+
/**
50+
* Insert one entity.
51+
*
52+
* @return Inserted entity.
53+
*/
54+
@Override
55+
T one(T object);
56+
57+
/**
58+
* Insert a collection of entities.
59+
*
60+
* @return Inserted entities
61+
*/
62+
@Override
63+
Collection<? extends T> all(Collection<? extends T> objects);
64+
65+
}
66+
67+
interface MutateInByIdWithPaths<T> extends TerminatingMutateInById<T>, WithMutateInPaths<T> {
68+
/**
69+
* Adds given paths to remove mutations.
70+
* See {@link com.couchbase.client.java.kv.Remove} for more details.
71+
* @param removePaths The property paths to removed from document.
72+
*/
73+
@Override
74+
MutateInByIdWithPaths<T> withRemovePaths(final String... removePaths);
75+
/**
76+
* Adds given paths to insert mutations.
77+
* See {@link com.couchbase.client.java.kv.Insert} for more details.
78+
* @param insertPaths The property paths to be inserted into the document.
79+
*/
80+
@Override
81+
MutateInByIdWithPaths<T> withInsertPaths(final String... insertPaths);
82+
/**
83+
* Adds given paths to upsert mutations.
84+
* See {@link com.couchbase.client.java.kv.Upsert} for more details.
85+
* @param upsertPaths The property paths to be upserted into the document.
86+
*/
87+
@Override
88+
MutateInByIdWithPaths<T> withUpsertPaths(final String... upsertPaths);
89+
/**
90+
* Adds given paths to replace mutations.
91+
* See {@link com.couchbase.client.java.kv.Replace} for more details.
92+
* @param replacePaths The property paths to be replaced in the document.
93+
*/
94+
@Override
95+
MutateInByIdWithPaths<T> withReplacePaths(final String... replacePaths);
96+
/**
97+
* Marks that the CAS value should be provided with the mutations to protect against concurrent modifications.
98+
* By default the CAS value is not provided.
99+
*/
100+
MutateInByIdWithPaths<T> withCasProvided();
101+
}
102+
103+
/**
104+
* Fluent method to specify options.
105+
*
106+
* @param <T> the entity type to use.
107+
*/
108+
interface MutateInByIdWithOptions<T> extends MutateInByIdWithPaths<T>, WithMutateInOptions<T> {
109+
/**
110+
* Fluent method to specify options to use for execution
111+
*
112+
* @param options to use for execution
113+
*/
114+
@Override
115+
TerminatingMutateInById<T> withOptions(MutateInOptions options);
116+
}
117+
118+
/**
119+
* Fluent method to specify the collection.
120+
*
121+
* @param <T> the entity type to use for the results.
122+
*/
123+
interface MutateInByIdInCollection<T> extends MutateInByIdWithOptions<T>, InCollection<Object> {
124+
/**
125+
* With a different collection
126+
*
127+
* @param collection the collection to use.
128+
*/
129+
@Override
130+
MutateInByIdWithOptions<T> inCollection(String collection);
131+
}
132+
133+
/**
134+
* Fluent method to specify the scope.
135+
*
136+
* @param <T> the entity type to use for the results.
137+
*/
138+
interface MutateInByIdInScope<T> extends MutateInByIdInCollection<T>, InScope<Object> {
139+
/**
140+
* With a different scope
141+
*
142+
* @param scope the scope to use.
143+
*/
144+
@Override
145+
MutateInByIdInCollection<T> inScope(String scope);
146+
}
147+
148+
interface MutateInByIdWithDurability<T> extends MutateInByIdInScope<T>, WithDurability<T> {
149+
@Override
150+
MutateInByIdInScope<T> withDurability(DurabilityLevel durabilityLevel);
151+
152+
@Override
153+
MutateInByIdInScope<T> withDurability(PersistTo persistTo, ReplicateTo replicateTo);
154+
155+
}
156+
157+
interface MutateInByIdWithExpiry<T> extends MutateInByIdWithDurability<T>, WithExpiry<T> {
158+
@Override
159+
MutateInByIdWithDurability<T> withExpiry(Duration expiry);
160+
}
161+
162+
/**
163+
* Provides methods for constructing KV operations in a fluent way.
164+
*
165+
* @param <T> the entity type to upsert
166+
*/
167+
interface ExecutableMutateInById<T> extends MutateInByIdWithExpiry<T> {}
168+
169+
}

0 commit comments

Comments
 (0)