Skip to content

Commit 405df74

Browse files
committed
DATACOUCH-519 - Docs and small changes for transaction support.
1 parent ef6fb9c commit 405df74

File tree

11 files changed

+176
-28
lines changed

11 files changed

+176
-28
lines changed

src/main/asciidoc/index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ include::{spring-data-commons-docs}/repositories.adoc[]
2323
include::repository.adoc[]
2424
include::reactiverepository.adoc[]
2525
include::template.adoc[]
26+
include::transactions.adoc[]
2627
// (daschl) disabled the ansijoins docs since it is being overhauled for 4.x
2728
// include::ansijoins.adoc[]
2829
:leveloffset: -1

src/main/asciidoc/transactions.adoc

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
[[couchbase.transactions]]
2+
= Transaction Support
3+
4+
Couchbase supports https://docs.couchbase.com/server/6.5/learn/data/transactions.html[Distributed Transactions]. This section documents on how to use it with Spring Data Couchbase.
5+
6+
== Requirements
7+
8+
- Couchbase Server 6.5 or above.
9+
- Couchbase Java client 3.0.0 or above. It is recommended to follow the transitive dependency for the transactions library from maven.
10+
- NTP should be configured so nodes of the Couchbase cluster are in sync with time. The time being out of sync will not cause incorrect behavior, but can impact metadata cleanup.
11+
12+
== Getting Started & Configuration
13+
14+
The `couchbase-transactions` artifact needs to be included into your `pom.xml` if maven is being used (or equivalent).
15+
16+
- Group: `com.couchbase.client`
17+
- Artifact: `couchbase-transactions`
18+
- Version: latest one, i.e. `1.0.0`
19+
20+
Once it is included in your project, you need to create a single `Transactions` object. Conveniently, it can be part of
21+
your spring data couchbase `AbstractCouchbaseConfiguration` implementation:
22+
23+
.Transaction Configuration
24+
====
25+
[source,java]
26+
----
27+
@Configuration
28+
static class Config extends AbstractCouchbaseConfiguration {
29+
30+
// Usual Setup
31+
@Override public String getConnectionString() { /* ... */ }
32+
@Override public String getUserName() { /* ... */ }
33+
@Override public String getPassword() { /* ... */ }
34+
@Override public String getBucketName() { /* ... */ }
35+
36+
@Bean
37+
public Transactions transactions(final Cluster couchbaseCluster) {
38+
return Transactions.create(couchbaseCluster, TransactionConfigBuilder.create()
39+
// The configuration can be altered here, but in most cases the defaults are fine.
40+
.build());
41+
}
42+
43+
}
44+
----
45+
====
46+
47+
Once the `@Bean` is configured, you can autowire it from your service (or any other class) to make use of it. Please
48+
see the https://docs.couchbase.com/java-sdk/3.0/howtos/distributed-acid-transactions-from-the-sdk.html[Reference Documentation]
49+
on how to use the `Transactions` class. Since you need access to the current `Collection` as well, we recommend you to also
50+
autowire the `CouchbaseClientFactory` and access it from there:
51+
52+
.Transaction Access
53+
====
54+
[source,java]
55+
----
56+
@Autowired
57+
Transactions transactions;
58+
59+
@Autowired
60+
CouchbaseClientFactory couchbaseClientFactory;
61+
62+
public void doSomething() {
63+
transactions.run(ctx -> {
64+
ctx.insert(couchbaseClientFactory.getDefaultCollection(), "id", "content");
65+
ctx.commit();
66+
});
67+
}
68+
----
69+
====
70+
71+
== Object Conversions
72+
73+
Since the transactions library itself has no knowledge of your spring data entity types, you need to convert it back and
74+
forth when reading/writing to interact properly. Fortunately, all you need to do is autowire the `MappingCouchbaseConverter` and
75+
utilize it:
76+
77+
.Transaction Conversion on Write
78+
====
79+
[source,java]
80+
----
81+
@Autowired
82+
MappingCouchbaseConverter mappingCouchbaseConverter;
83+
84+
public void doSomething() {
85+
transactions.run(ctx -> {
86+
87+
Airline airline = new Airline("demo-airline", "at");
88+
CouchbaseDocument target = new CouchbaseDocument();
89+
mappingCouchbaseConverter.write(airline, target);
90+
91+
ctx.insert(couchbaseClientFactory.getDefaultCollection(), target.getId(), target.getContent());
92+
93+
ctx.commit();
94+
});
95+
}
96+
----
97+
====
98+
99+
The same approach can be used on read:
100+
101+
.Transaction Conversion on Read
102+
====
103+
[source,java]
104+
----
105+
TransactionGetResult getResult = ctx.get(couchbaseClientFactory.getDefaultCollection(), "doc-id");
106+
107+
CouchbaseDocument source = new CouchbaseDocument(getResult.id());
108+
source.setContent(getResult.contentAsObject());
109+
Airline read = mappingCouchbaseConverter.read(Airline.class, source);
110+
----
111+
====
112+
113+
We are also looking into tighter integration of the transaction library into the spring data library
114+
ecosystem.

src/main/java/org/springframework/data/couchbase/CouchbaseClientFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public interface CouchbaseClientFactory extends Closeable {
5555
*/
5656
Collection getCollection(String name);
5757

58+
/**
59+
* Provides access to the default collection.
60+
*/
61+
Collection getDefaultCollection();
62+
5863
/**
5964
* Returns a new {@link CouchbaseClientFactory} set to the scope given as an argument.
6065
*

src/main/java/org/springframework/data/couchbase/SimpleCouchbaseClientFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public Collection getCollection(final String collectionName) {
104104
return scope.collection(collectionName);
105105
}
106106

107+
@Override
108+
public Collection getDefaultCollection() {
109+
return getCollection(null);
110+
}
111+
107112
@Override
108113
public PersistenceExceptionTranslator getExceptionTranslator() {
109114
return exceptionTranslator;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public Mono<T> one(T object) {
6868
return Mono.just(object).flatMap(o -> {
6969
CouchbaseDocument converted = template.support().encodeEntity(o);
7070
return template.getCollection(collection).reactive()
71-
.insert(converted.getId(), converted.getPayload(), buildInsertOptions()).map(result -> {
71+
.insert(converted.getId(), converted.getContent(), buildInsertOptions()).map(result -> {
7272
template.support().applyUpdatedCas(object, result.cas());
7373
return o;
7474
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public Mono<T> one(T object) {
6868
return Mono.just(object).flatMap(o -> {
6969
CouchbaseDocument converted = template.support().encodeEntity(o);
7070
return template.getCollection(collection).reactive()
71-
.replace(converted.getId(), converted.getPayload(), buildReplaceOptions()).map(result -> {
71+
.replace(converted.getId(), converted.getContent(), buildReplaceOptions()).map(result -> {
7272
template.support().applyUpdatedCas(object, result.cas());
7373
return o;
7474
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public Mono<T> one(T object) {
6868
return Mono.just(object).flatMap(o -> {
6969
CouchbaseDocument converted = template.support().encodeEntity(o);
7070
return template.getCollection(collection).reactive()
71-
.upsert(converted.getId(), converted.getPayload(), buildUpsertOptions()).map(result -> {
71+
.upsert(converted.getId(), converted.getContent(), buildUpsertOptions()).map(result -> {
7272
template.support().applyUpdatedCas(object, result.cas());
7373
return o;
7474
});

src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ protected Map<Object, Object> readMap(final TypeInformation<?> type, final Couch
329329

330330
Class<?> mapType = typeMapper.readType(source, type).getType();
331331
Map<Object, Object> map = CollectionFactory.createMap(mapType, source.export().keySet().size());
332-
Map<String, Object> sourceMap = source.getPayload();
332+
Map<String, Object> sourceMap = source.getContent();
333333

334334
for (Map.Entry<String, Object> entry : sourceMap.entrySet()) {
335335
Object key = entry.getKey();

src/main/java/org/springframework/data/couchbase/core/mapping/CouchbaseDocument.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.data.couchbase.core.mapping;
1818

19+
import com.couchbase.client.java.json.JsonObject;
20+
1921
import java.util.HashMap;
2022
import java.util.Map;
2123

@@ -46,7 +48,7 @@ public class CouchbaseDocument implements CouchbaseStorable {
4648
/**
4749
* Contains the actual data to be stored.
4850
*/
49-
private HashMap<String, Object> payload;
51+
private Map<String, Object> content;
5052

5153
/**
5254
* Represents the document ID used to identify the document in the bucket.
@@ -83,7 +85,7 @@ public CouchbaseDocument(final String id) {
8385
public CouchbaseDocument(final String id, final int expiration) {
8486
this.id = id;
8587
this.expiration = expiration;
86-
payload = new HashMap<String, Object>();
88+
content = new HashMap<>();
8789
}
8890

8991
/**
@@ -96,7 +98,7 @@ public CouchbaseDocument(final String id, final int expiration) {
9698
public final CouchbaseDocument put(final String key, final Object value) {
9799
verifyValueType(value);
98100

99-
payload.put(key, value);
101+
content.put(key, value);
100102
return this;
101103
}
102104

@@ -107,7 +109,7 @@ public final CouchbaseDocument put(final String key, final Object value) {
107109
* @return the value to which the specified key is mapped, or null if does not contain a mapping for the key.
108110
*/
109111
public final Object get(final String key) {
110-
return payload.get(key);
112+
return content.get(key);
111113
}
112114

113115
/**
@@ -118,8 +120,8 @@ public final Object get(final String key) {
118120
* @return
119121
*/
120122
public final HashMap<String, Object> export() {
121-
HashMap<String, Object> toExport = new HashMap<String, Object>(payload);
122-
for (Map.Entry<String, Object> entry : payload.entrySet()) {
123+
HashMap<String, Object> toExport = new HashMap<String, Object>(content);
124+
for (Map.Entry<String, Object> entry : content.entrySet()) {
123125
if (entry.getValue() instanceof CouchbaseDocument) {
124126
toExport.put(entry.getKey(), ((CouchbaseDocument) entry.getValue()).export());
125127
} else if (entry.getValue() instanceof CouchbaseList) {
@@ -136,7 +138,7 @@ public final HashMap<String, Object> export() {
136138
* @return true if it contains a payload for the specified key.
137139
*/
138140
public final boolean containsKey(final String key) {
139-
return payload.containsKey(key);
141+
return content.containsKey(key);
140142
}
141143

142144
/**
@@ -146,7 +148,7 @@ public final boolean containsKey(final String key) {
146148
* @return true if it contains the specified value.
147149
*/
148150
public final boolean containsValue(final Object value) {
149-
return payload.containsValue(value);
151+
return content.containsValue(value);
150152
}
151153

152154
/**
@@ -165,14 +167,14 @@ public final int size() {
165167
* @return the size of the attributes in this and recursive documents.
166168
*/
167169
public final int size(final boolean recursive) {
168-
int thisSize = payload.size();
170+
int thisSize = content.size();
169171

170172
if (!recursive || thisSize == 0) {
171173
return thisSize;
172174
}
173175

174176
int totalSize = thisSize;
175-
for (Object value : payload.values()) {
177+
for (Object value : content.values()) {
176178
if (value instanceof CouchbaseDocument) {
177179
totalSize += ((CouchbaseDocument) value).size(true);
178180
} else if (value instanceof CouchbaseList) {
@@ -192,8 +194,29 @@ public final int size(final boolean recursive) {
192194
*
193195
* @return the underlying payload.
194196
*/
195-
public HashMap<String, Object> getPayload() {
196-
return payload;
197+
public Map<String, Object> getContent() {
198+
return content;
199+
}
200+
201+
/**
202+
* Allows to set the full payload as a map.
203+
*
204+
* @param content the payload to set
205+
* @return this document for chaining purposes.
206+
*/
207+
public CouchbaseDocument setContent(final Map<String, Object> content) {
208+
this.content = content;
209+
return this;
210+
}
211+
212+
/**
213+
* Allows to set the full payload as a json object for convenience.
214+
*
215+
* @param payload the payload to set
216+
* @return this document for chaining purposes.
217+
*/
218+
public CouchbaseDocument setContent(final JsonObject payload) {
219+
return setContent(payload.toMap());
197220
}
198221

199222
/**
@@ -271,6 +294,6 @@ private void verifyValueType(final Object value) {
271294
*/
272295
@Override
273296
public String toString() {
274-
return "CouchbaseDocument{" + "id=" + id + ", exp=" + expiration + ", payload=" + payload + '}';
297+
return "CouchbaseDocument{" + "id=" + id + ", exp=" + expiration + ", content=" + content + '}';
275298
}
276299
}

src/test/java/org/springframework/data/couchbase/core/mapping/CustomConvertersTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void shouldWriteWithCustomConverter() {
6060
CouchbaseDocument doc = new CouchbaseDocument();
6161
converter.write(post, doc);
6262

63-
assertThat(doc.getPayload().get("created")).isEqualTo(date.toString());
63+
assertThat(doc.getContent().get("created")).isEqualTo(date.toString());
6464
}
6565

6666
@Test
@@ -71,7 +71,7 @@ void shouldReadWithCustomConverter() {
7171
converter.afterPropertiesSet();
7272

7373
CouchbaseDocument doc = new CouchbaseDocument();
74-
doc.getPayload().put("content", 10);
74+
doc.getContent().put("content", 10);
7575
Counter loaded = converter.read(Counter.class, doc);
7676
assertThat(loaded.content).isEqualTo("even");
7777
}
@@ -90,8 +90,8 @@ void shouldWriteConvertFullDocument() {
9090
CouchbaseDocument doc = new CouchbaseDocument();
9191
converter.write(post, doc);
9292

93-
assertThat(doc.getPayload().get("title")).isEqualTo("The Foo of the Bar");
94-
assertThat(doc.getPayload().get("slug")).isEqualTo("the_foo_of_the_bar");
93+
assertThat(doc.getContent().get("title")).isEqualTo("The Foo of the Bar");
94+
assertThat(doc.getContent().get("slug")).isEqualTo("the_foo_of_the_bar");
9595
}
9696

9797
@Test
@@ -102,7 +102,7 @@ void shouldReadConvertFullDocument() {
102102
converter.afterPropertiesSet();
103103

104104
CouchbaseDocument doc = new CouchbaseDocument();
105-
doc.getPayload().put("title", "My Title");
105+
doc.getContent().put("title", "My Title");
106106

107107
BlogPost loaded = converter.read(BlogPost.class, doc);
108108
assertThat(loaded.id).isEqualTo("modified");
@@ -146,7 +146,7 @@ public enum CouchbaseDocumentToBlogPostConverter implements Converter<CouchbaseD
146146
public BlogPost convert(CouchbaseDocument source) {
147147
BlogPost post = new BlogPost();
148148
post.id = "modified";
149-
post.title = source.getPayload().get("title") + "!!";
149+
post.title = source.getContent().get("title") + "!!";
150150
return post;
151151
}
152152
}

src/test/java/org/springframework/data/couchbase/core/mapping/MappingCouchbaseConverterTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ void writesAndReadsCustomConvertedClass() {
399399
mapOfValuesDoc.put("val2", value2Str);
400400
source.put("mapOfValues", mapOfValuesDoc);
401401

402-
assertThat(valueStr).isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")).get(0));
403-
assertThat(value2Str).isEqualTo(((CouchbaseList) converted.getPayload().get("listOfValues")).get(1));
402+
assertThat(valueStr).isEqualTo(((CouchbaseList) converted.getContent().get("listOfValues")).get(0));
403+
assertThat(value2Str).isEqualTo(((CouchbaseList) converted.getContent().get("listOfValues")).get(1));
404404
assertThat(converted.export().toString()).isEqualTo(source.export().toString());
405405

406406
CustomEntity readConverted = converter.read(CustomEntity.class, source);
@@ -463,10 +463,10 @@ void writesAndReadsDates() {
463463

464464
CouchbaseDocument converted = new CouchbaseDocument();
465465
converter.write(entity, converted);
466-
assertThat(converted.getPayload().get("created")).isEqualTo(created.getTime());
467-
assertThat(converted.getPayload().get("modified")).isEqualTo(modified.getTimeInMillis() / 1000);
466+
assertThat(converted.getContent().get("created")).isEqualTo(created.getTime());
467+
assertThat(converted.getContent().get("modified")).isEqualTo(modified.getTimeInMillis() / 1000);
468468
LocalDateTimeToLongConverter localDateTimeToDateconverter = LocalDateTimeToLongConverter.INSTANCE;
469-
assertThat(converted.getPayload().get("deleted")).isEqualTo(localDateTimeToDateconverter.convert(deleted));
469+
assertThat(converted.getContent().get("deleted")).isEqualTo(localDateTimeToDateconverter.convert(deleted));
470470

471471
DateEntity read = converter.read(DateEntity.class, converted);
472472
assertThat(read.created.getTime()).isEqualTo(created.getTime());

0 commit comments

Comments
 (0)