Skip to content

Commit d294b2f

Browse files
committed
Add support for Couchbase Transactions.
Closes #1145.
1 parent 37648c8 commit d294b2f

File tree

56 files changed

+1232
-284
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1232
-284
lines changed

pom.xml

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<couchbase>3.2.1</couchbase>
2222
<couchbase.osgi>3.2.1</couchbase.osgi>
2323
<springdata.commons>2.6.0-SNAPSHOT</springdata.commons>
24+
<couchbase-transactions>1.2.1-SNAPSHOT</couchbase-transactions>
2425
<java-module-name>spring.data.couchbase</java-module-name>
2526
</properties>
2627

@@ -37,6 +38,14 @@
3738
</dependencyManagement>
3839

3940
<dependencies>
41+
42+
<dependency>
43+
<groupId>com.couchbase.client</groupId>
44+
<artifactId>couchbase-transactions</artifactId>
45+
<version>${couchbase-transactions}</version>
46+
</dependency>
47+
48+
4049
<dependency>
4150
<groupId>org.springframework</groupId>
4251
<artifactId>spring-context-support</artifactId>

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ public Scope getScope() {
9797
@Override
9898
public Collection getCollection(final String collectionName) {
9999
final Scope scope = getScope();
100-
if (collectionName == null) {
100+
if (collectionName == null || CollectionIdentifier.DEFAULT_COLLECTION.equals(collectionName)) {
101101
if (!scope.name().equals(CollectionIdentifier.DEFAULT_SCOPE)) {
102-
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used!");
102+
throw new IllegalStateException("A collectionName must be provided if a non-default scope is used");
103103
}
104104
return getBucket().defaultCollection();
105105
}

src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
package org.springframework.data.couchbase.config;
1818

1919
import static com.couchbase.client.java.ClusterOptions.clusterOptions;
20+
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_MAPPING_CONTEXT;
21+
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TRANSACTIONS;
2022

23+
import java.time.Duration;
2124
import java.util.Collections;
2225
import java.util.HashSet;
2326
import java.util.Set;
@@ -46,6 +49,7 @@
4649
import org.springframework.util.ClassUtils;
4750
import org.springframework.util.StringUtils;
4851

52+
import com.couchbase.client.core.cnc.Event;
4953
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
5054
import com.couchbase.client.core.encryption.CryptoManager;
5155
import com.couchbase.client.core.env.Authenticator;
@@ -57,6 +61,10 @@
5761
import com.couchbase.client.java.env.ClusterEnvironment;
5862
import com.couchbase.client.java.json.JacksonTransformers;
5963
import com.couchbase.client.java.json.JsonValueModule;
64+
import com.couchbase.transactions.TransactionDurabilityLevel;
65+
import com.couchbase.transactions.Transactions;
66+
import com.couchbase.transactions.config.TransactionConfig;
67+
import com.couchbase.transactions.config.TransactionConfigBuilder;
6068
import com.fasterxml.jackson.databind.ObjectMapper;
6169

6270
/**
@@ -122,7 +130,7 @@ protected Authenticator authenticator() {
122130
* @param couchbaseCluster the cluster reference from the SDK.
123131
* @return the initialized factory.
124132
*/
125-
@Bean
133+
@Bean(name = BeanNames.COUCHBASE_CLIENT_FACTORY)
126134
public CouchbaseClientFactory couchbaseClientFactory(final Cluster couchbaseCluster) {
127135
return new SimpleCouchbaseClientFactory(couchbaseCluster, getBucketName(), getScopeName());
128136
}
@@ -283,7 +291,7 @@ public TranslationService couchbaseTranslationService() {
283291
*
284292
* @throws Exception on Bean construction failure.
285293
*/
286-
@Bean
294+
@Bean(COUCHBASE_MAPPING_CONTEXT)
287295
public CouchbaseMappingContext couchbaseMappingContext(CustomConversions customConversions) throws Exception {
288296
CouchbaseMappingContext mappingContext = new CouchbaseMappingContext();
289297
mappingContext.setInitialEntitySet(getInitialEntitySet());
@@ -312,6 +320,16 @@ public ObjectMapper couchbaseObjectMapper() {
312320
return mapper;
313321
}
314322

323+
@Bean(COUCHBASE_TRANSACTIONS)
324+
public Transactions getTransactions(Cluster cluster) {
325+
return Transactions.create(cluster, getTransactionConfig());
326+
}
327+
328+
TransactionConfig getTransactionConfig() {
329+
return TransactionConfigBuilder.create().logDirectly(Event.Severity.INFO).logOnFailure(true, Event.Severity.ERROR)
330+
.expirationTime(Duration.ofMinutes(10)).durabilityLevel(TransactionDurabilityLevel.NONE).build();
331+
}
332+
315333
/**
316334
* Configure whether to automatically create indices for domain types by deriving the from the entity or not.
317335
*/

src/main/java/org/springframework/data/couchbase/config/BeanNames.java

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class BeanNames {
3434

3535
public static final String COUCHBASE_CUSTOM_CONVERSIONS = "couchbaseCustomConversions";
3636

37+
public static final String COUCHBASE_TRANSACTIONS = "couchbaseTransactions";
38+
3739
/**
3840
* The name for the bean that stores custom mapping between repositories and their backing couchbaseOperations.
3941
*/
@@ -59,4 +61,7 @@ public class BeanNames {
5961
* The name for the bean that will handle reactive audit trail marking of entities.
6062
*/
6163
public static final String REACTIVE_COUCHBASE_AUDITING_HANDLER = "reactiveCouchbaseAuditingHandler";
64+
65+
public static final String COUCHBASE_CLIENT_FACTORY = "couchbaseClientFactory";
66+
6267
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.springframework.data.couchbase.core;
1919

20+
import java.util.List;
21+
2022
import org.slf4j.Logger;
2123
import org.slf4j.LoggerFactory;
2224
import org.springframework.beans.BeansException;
@@ -34,11 +36,14 @@
3436
import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
3537
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
3638
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
39+
import org.springframework.data.couchbase.core.query.N1qlJoin;
3740
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
3841
import org.springframework.data.mapping.PersistentPropertyAccessor;
42+
import org.springframework.data.mapping.PropertyHandler;
3943
import org.springframework.data.mapping.callback.EntityCallbacks;
4044
import org.springframework.data.mapping.context.MappingContext;
4145
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
46+
import org.springframework.data.util.TypeInformation;
4247
import org.springframework.util.Assert;
4348

4449
/**
@@ -218,4 +223,8 @@ protected <T> T maybeCallAfterConvert(T object, CouchbaseDocument document, Stri
218223
return object;
219224
}
220225

226+
public CouchbaseTemplate getTemplate() {
227+
return template;
228+
}
229+
221230
}

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

+22-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
import java.time.Duration;
1919
import java.util.Collection;
2020

21-
import org.springframework.data.couchbase.core.support.OneAndAllId;
2221
import org.springframework.data.couchbase.core.support.InCollection;
22+
import org.springframework.data.couchbase.core.support.InScope;
23+
import org.springframework.data.couchbase.core.support.OneAndAllId;
2324
import org.springframework.data.couchbase.core.support.WithGetOptions;
2425
import org.springframework.data.couchbase.core.support.WithProjectionId;
25-
import org.springframework.data.couchbase.core.support.InScope;
26+
import org.springframework.data.couchbase.core.support.WithTransaction;
2627

2728
import com.couchbase.client.java.kv.GetOptions;
2829
import org.springframework.data.couchbase.core.support.WithExpiry;
30+
import com.couchbase.transactions.AttemptContextReactive;
2931

3032
/**
3133
* Get Operations
@@ -132,11 +134,28 @@ interface FindByIdWithExpiry<T> extends FindByIdWithProjection<T>, WithExpiry<T>
132134
FindByIdWithProjection<T> withExpiry(Duration expiry);
133135
}
134136

137+
/**
138+
* Provide attempt context
139+
*
140+
* @param <T> the entity type to use for the results
141+
*/
142+
interface FindByIdWithTransaction<T> extends FindByIdWithExpiry<T>, WithTransaction<T> {
143+
/**
144+
* Finds the distinct values for a specified {@literal field} across a single collection
145+
*
146+
* @param txCtx Must not be {@literal null}.
147+
* @return new instance of {@link ExecutableFindById}.
148+
* @throws IllegalArgumentException if field is {@literal null}.
149+
*/
150+
@Override
151+
FindByIdWithProjection<T> transaction(AttemptContextReactive txCtx);
152+
}
153+
135154
/**
136155
* Provides methods for constructing query operations in a fluent way.
137156
*
138157
* @param <T> the entity type to use for the results
139158
*/
140-
interface ExecutableFindById<T> extends FindByIdWithExpiry<T> {}
159+
interface ExecutableFindById<T> extends FindByIdWithTransaction<T> {}
141160

142161
}

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

+18-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.util.Assert;
2525

2626
import com.couchbase.client.java.kv.GetOptions;
27+
import com.couchbase.transactions.AttemptContextReactive;
2728

2829
public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOperation {
2930

@@ -35,7 +36,7 @@ public class ExecutableFindByIdOperationSupport implements ExecutableFindByIdOpe
3536

3637
@Override
3738
public <T> ExecutableFindById<T> findById(Class<T> domainType) {
38-
return new ExecutableFindByIdSupport<>(template, domainType, null, null, null, null, null);
39+
return new ExecutableFindByIdSupport<>(template, domainType, null, null, null, null, null, null);
3940
}
4041

4142
static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
@@ -47,19 +48,21 @@ static class ExecutableFindByIdSupport<T> implements ExecutableFindById<T> {
4748
private final GetOptions options;
4849
private final List<String> fields;
4950
private final Duration expiry;
51+
private final AttemptContextReactive txCtx;
5052
private final ReactiveFindByIdSupport<T> reactiveSupport;
5153

5254
ExecutableFindByIdSupport(CouchbaseTemplate template, Class<T> domainType, String scope, String collection,
53-
GetOptions options, List<String> fields, Duration expiry) {
55+
GetOptions options, List<String> fields, Duration expiry, AttemptContextReactive txCtx) {
5456
this.template = template;
5557
this.domainType = domainType;
5658
this.scope = scope;
5759
this.collection = collection;
5860
this.options = options;
5961
this.fields = fields;
6062
this.expiry = expiry;
63+
this.txCtx = txCtx;
6164
this.reactiveSupport = new ReactiveFindByIdSupport<>(template.reactive(), domainType, scope, collection, options,
62-
fields, expiry, new NonReactiveSupportWrapper(template.support()));
65+
fields, expiry, txCtx, new NonReactiveSupportWrapper(template.support()));
6366
}
6467

6568
@Override
@@ -75,29 +78,35 @@ public Collection<? extends T> all(final Collection<String> ids) {
7578
@Override
7679
public TerminatingFindById<T> withOptions(final GetOptions options) {
7780
Assert.notNull(options, "Options must not be null.");
78-
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry);
81+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, txCtx);
7982
}
8083

8184
@Override
8285
public FindByIdWithOptions<T> inCollection(final String collection) {
83-
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry);
86+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, txCtx);
8487
}
8588

8689
@Override
8790
public FindByIdInCollection<T> inScope(final String scope) {
88-
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry);
91+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, txCtx);
8992
}
9093

9194
@Override
9295
public FindByIdInScope<T> project(String... fields) {
9396
Assert.notEmpty(fields, "Fields must not be null.");
94-
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields), expiry);
97+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, Arrays.asList(fields),
98+
expiry, txCtx);
9599
}
96100

97101
@Override
98102
public FindByIdWithProjection<T> withExpiry(final Duration expiry) {
99-
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields,
100-
expiry);
103+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, txCtx);
104+
}
105+
106+
@Override
107+
public FindByIdWithExpiry<T> transaction(AttemptContextReactive txCtx) {
108+
Assert.notNull(txCtx, "txCtx must not be null.");
109+
return new ExecutableFindByIdSupport<>(template, domainType, scope, collection, options, fields, expiry, txCtx);
101110
}
102111

103112
}

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

+22-2
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
import org.springframework.data.couchbase.core.support.WithDistinct;
3030
import org.springframework.data.couchbase.core.support.WithQuery;
3131
import org.springframework.data.couchbase.core.support.WithQueryOptions;
32+
import org.springframework.data.couchbase.core.support.WithTransaction;
3233
import org.springframework.lang.Nullable;
3334

3435
import com.couchbase.client.java.query.QueryOptions;
3536
import com.couchbase.client.java.query.QueryScanConsistency;
37+
import com.couchbase.transactions.AttemptContextReactive;
3638

3739
/**
3840
* Query Operations
@@ -287,14 +289,32 @@ interface FindByQueryWithDistinct<T> extends FindByQueryWithProjecting<T>, WithD
287289
* @throws IllegalArgumentException if field is {@literal null}.
288290
*/
289291
@Override
290-
FindByQueryWithProjection<T> distinct(String[] distinctFields);
292+
FindByQueryWithProjecting<T> distinct(String[] distinctFields);
293+
}
294+
295+
/**
296+
* Fluent method to specify transaction
297+
*
298+
* @param <T> the entity type to use for the results.
299+
*/
300+
interface FindByQueryWithTransaction<T> extends FindByQueryWithDistinct<T>, WithTransaction<T> {
301+
302+
/**
303+
* Finds the distinct values for a specified {@literal field} across a single collection
304+
*
305+
* @param txCtx Must not be {@literal null}.
306+
* @return new instance of {@link ExecutableFindByQuery}.
307+
* @throws IllegalArgumentException if field is {@literal null}.
308+
*/
309+
@Override
310+
FindByQueryWithDistinct<T> transaction(AttemptContextReactive txCtx);
291311
}
292312

293313
/**
294314
* Provides methods for constructing query operations in a fluent way.
295315
*
296316
* @param <T> the entity type to use for the results
297317
*/
298-
interface ExecutableFindByQuery<T> extends FindByQueryWithDistinct<T> {}
318+
interface ExecutableFindByQuery<T> extends FindByQueryWithTransaction<T> {}
299319

300320
}

0 commit comments

Comments
 (0)