Skip to content

Commit 6b8c026

Browse files
committed
Update transactions documentation for native transaction support.
Closes #1454,#1512.
1 parent 4974e00 commit 6b8c026

11 files changed

+282
-168
lines changed

README.adoc

+23
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,29 @@ Building the documentation builds also the project without running tests.
179179

180180
The generated documentation is available from `target/site/reference/html/index.html`.
181181

182+
=== Building and staging reference documentation for review
183+
184+
[source,bash]
185+
----
186+
export MY_GIT_USER=<github-user>
187+
mvn generate-resources
188+
docs=`pwd`/target/site/reference/html
189+
pushd /tmp
190+
mkdir $$
191+
cd $$
192+
# see https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site
193+
# this examples uses a repository named "staged"
194+
git clone [email protected]:${MY_GIT_USER}/staged.git -b gh-pages
195+
cd staged
196+
cp -R $docs/* .
197+
git add .
198+
git commit --message "stage for review"
199+
git push origin gh-pages
200+
popd
201+
----
202+
203+
The generated documentation is available from `target/site/reference/html/index.html`.
204+
182205
== Examples
183206

184207
* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.

pom.xml

+12
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,20 @@
301301
<artifactId>maven-assembly-plugin</artifactId>
302302
</plugin>
303303
<plugin>
304+
<!-- generate asciidoc to stage for review -->
304305
<groupId>org.asciidoctor</groupId>
305306
<artifactId>asciidoctor-maven-plugin</artifactId>
307+
<executions>
308+
<execution>
309+
<phase>generate-resources</phase>
310+
<goals>
311+
<goal>process-asciidoc</goal>
312+
</goals>
313+
<configuration>
314+
<outputDirectory>target/site/reference/html</outputDirectory>
315+
</configuration>
316+
</execution>
317+
</executions>
306318
</plugin>
307319
<plugin>
308320
<groupId>com.mysema.maven</groupId>

src/main/asciidoc/transactions.adoc

+141-64
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,191 @@
11
[[couchbase.transactions]]
2-
= Transaction Support
2+
= Couchbase Transactions
33

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.
4+
Couchbase supports https://docs.couchbase.com/server/current/learn/data/transactions.html[Distributed Transactions]. This section documents how to use it with Spring Data Couchbase.
55

66
== Requirements
77

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.
8+
- Couchbase Server 6.6.1 or aabove.
9+
- Spring Data Couchbase 5.0.0-M5 or above.
1010
- 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.
1111

1212
== Getting Started & Configuration
1313

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:
14+
Couchbase Transactions are normally leveraged with the @Transactional operator.
15+
The @Transactional operator is implemented with the CouchbaseTransactionManager which is supplied as a bean in the AbstractCouchbaseConfiguration.
2216

2317
.Transaction Configuration
2418
====
2519
[source,java]
2620
----
2721
@Configuration
22+
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
23+
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
24+
@EnableTransactionManagement // Add for @Transactional annotation support
2825
static class Config extends AbstractCouchbaseConfiguration {
2926
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-
27+
// Usual Setup
28+
@Override public String getConnectionString() { /* ... */ }
29+
@Override public String getUserName() { /* ... */ }
30+
@Override public String getPassword() { /* ... */ }
31+
@Override public String getBucketName() { /* ... */ }
32+
33+
// Customization of transaction behavior is via the configureEnvironment() method
34+
@Override
35+
public void configureEnvironment(ClusterEnvironment.Builder builder) {
36+
// allow really long transactions
37+
builder.transactionsConfig(
38+
TransactionsConfig.builder().timeout(Duration.ofSeconds(30)));
39+
}
4340
}
4441
----
4542
====
4643

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:
44+
45+
Functioning of the @Transactional method annotation requires
46+
[start=1]
47+
. the configuration class to be annotated with @EnableTransactionManagement;
48+
. the service object with the annotated methods must be annotated with @Service and @Component;
49+
. the service object with the annotated methods must be obtained as an @Bean either via @Autowire or
50+
from the application context;
51+
. the call to the method must be made from a different class than service because calling an annotated
52+
method from the same class will not invoke the Method Interceptor that does the transaction processing.
53+
54+
== Transactions with @Transactional
55+
56+
@Transactional Defines as transactional a method or all methods on a class.
57+
58+
When this annotation is declared at the class level, it applies as a default
59+
to all methods of the declaring class and its subclasses. Note that it does not
60+
apply to ancestor classes up the class hierarchy; inherited methods need to be
61+
locally redeclared in order to participate in a subclass-level annotation. For
62+
details on method visibility constraints, consult the
63+
https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction[Transaction Management]
64+
section of the reference manual.
65+
66+
=== Attribute Semantics
67+
68+
In this release, he Couchbase Transactions implementation in
69+
Spring Data Couchbase ignores all @Transactional attributes.
70+
The transaction will roll back on uncaught exceptions.
71+
The transaction isoloation level is read committed;
72+
73+
=== Transaction Management
74+
75+
Blocking transactions are thread-bound and therefore do not propagate to newly started threads within the method.
76+
Non-blocking transactions are bound to the reactive context.
5177

5278
.Transaction Access
5379
====
5480
[source,java]
5581
----
56-
@Autowired
57-
Transactions transactions;
82+
// this object must be provided by the Spring framework either by @Autowire or from ac.getBean().
83+
// If created directly with its constructor, the method interceptor will not be called.
84+
@Autowired PersonService personService;
85+
86+
Person walterWhite = new Person( "Walter", "White");
87+
Person p = personService.save(walterWhite);
88+
89+
----------
90+
91+
import reactor.core.publisher.Mono;
92+
import reactor.core.publisher.Flux;
93+
94+
import org.springframework.stereotype.Component;
95+
import org.springframework.stereotype.Service;
96+
import org.springframework.transaction.annotation.Transactional;
97+
98+
final CouchbaseOperations personOperations;
99+
final ReactiveCouchbaseOperations reactivePersonOperations;
100+
101+
@Service
102+
@Component
103+
public class PersonService {
104+
105+
final CouchbaseOperations operations;
106+
final ReactiveCouchbaseOperations reactiveOperations;
107+
108+
// This method will be called for @Autowired using the
109+
// CouchbaseOperations and ReactiveCouchbaseOperations @Beans
110+
public PersonService(CouchbaseOperations ops, ReactiveCouchbaseOperations reactiveOps) {
111+
operations = ops;
112+
reactiveOperations = reactiveOps;
113+
}
114+
115+
@Transactional
116+
public Person save(Person p) {
117+
return operations.save(p);
118+
}
119+
120+
@Transactional
121+
public Person changeFirstName(String id, String newFirstName) {
122+
Person p = operations.findById(Person.class).one(id);
123+
return operations.replaceById(Person.class).one(p.withFirstName(newFirstName);
124+
}
125+
126+
@Transactional
127+
public Mono<Person> reactiveChangeFirstName(String id, String newFirstName) {
128+
return personOperationsRx.findById(Person.class).one(person.id())
129+
.flatMap(p -> personOperationsRx.replaceById(Person.class).one(p.withFirstName(newFirstName)));
130+
}
58131
59-
@Autowired
60-
CouchbaseClientFactory couchbaseClientFactory;
61132
62-
public void doSomething() {
63-
transactions.run(ctx -> {
64-
ctx.insert(couchbaseClientFactory.getDefaultCollection(), "id", "content");
65-
ctx.commit();
66-
});
67133
}
68134
----
69135
====
70136

71-
== Object Conversions
137+
== Transactions with CouchbaseTransactionalOperator
72138

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:
139+
CouchbaseTransactionalOperator can be used to construct a tansaction in-line without creating a service class that uses @Transactional.
140+
CouchbaseTransactionalOperator is available as a bean and can be instantiated with @Autowire.
141+
If creating one explicitly, it must be created with the following (and not TransactionalOperator.create(manager) as described in general Spring Data transaction documenation).
142+
.TransactionalOperator Construction
143+
====
144+
[source,java]
145+
----
146+
public static CouchbaseTransactionalOperator create(CouchbaseCallbackTransactionManager manager)
147+
====
76148

77-
.Transaction Conversion on Write
149+
.Transaction Access Using TransactionalOperator.execute()
78150
====
79151
[source,java]
80152
----
81-
@Autowired
82-
MappingCouchbaseConverter mappingCouchbaseConverter;
153+
@Autowired TransactionalOperator txOperator;
154+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
83155
84-
public void doSomething() {
85-
transactions.run(ctx -> {
156+
Person person = reactiveCouchbaseTemplate.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
157+
Flux<Person> result = txOperator.execute((ctx) -> reactiveCouchbaseTemplate.findById(Person.class).one(person.id())
158+
.flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt"))));
159+
result.blockLast();
160+
----
161+
====
86162

87-
Airline airline = new Airline("demo-airline", "at");
88-
CouchbaseDocument target = new CouchbaseDocument();
89-
mappingCouchbaseConverter.write(airline, target);
163+
== Transactions Directly with the SDK
90164

91-
ctx.insert(couchbaseClientFactory.getDefaultCollection(), target.getId(), target.getContent());
165+
Please see the https://docs.couchbase.com/java-sdk/current/howtos/distributed-acid-transactions-from-the-sdk.html[Reference Documentation]
92166

93-
ctx.commit();
94-
});
95-
}
96-
----
167+
.Transaction Access - Blocking
97168
====
169+
[source,java]
170+
----
171+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
98172
99-
The same approach can be used on read:
100-
101-
.Transaction Conversion on Read
173+
TransactionResult result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().transactions().run(ctx -> {
174+
return reactiveCouchbaseTemplate.insertById(Person.class).one(WalterWhite);
175+
});
176+
----
177+
====
178+
.Transaction Access - Reactive
102179
====
103180
[source,java]
104181
----
105-
TransactionGetResult getResult = ctx.get(couchbaseClientFactory.getDefaultCollection(), "doc-id");
182+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
106183
107-
CouchbaseDocument source = new CouchbaseDocument(getResult.id());
108-
source.setContent(getResult.contentAsObject());
109-
Airline read = mappingCouchbaseConverter.read(Airline.class, source);
184+
TransactionResult result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().reactive().transactions()
185+
.run(ctx -> TransactionalSupport.checkForTransactionInThreadLocalStorage().then(Mono.defer(() -> {
186+
reactiveCouchbaseTemplate.insertById(Person.class).one(WalterWhite);
187+
}))).block();
110188
----
111189
====
112190

113-
We are also looking into tighter integration of the transaction library into the spring data library
114-
ecosystem.
191+

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

+11
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
5252
import org.springframework.transaction.interceptor.TransactionAttributeSource;
5353
import org.springframework.transaction.interceptor.TransactionInterceptor;
54+
import org.springframework.transaction.support.TransactionTemplate;
5455
import org.springframework.util.ClassUtils;
5556
import org.springframework.util.StringUtils;
5657

@@ -330,6 +331,16 @@ CouchbaseCallbackTransactionManager couchbaseTransactionManager(CouchbaseClientF
330331
return new CouchbaseCallbackTransactionManager(clientFactory);
331332
}
332333

334+
/**
335+
* The default transaction template manager.
336+
*
337+
* @param couchbaseTransactionManager
338+
* @return
339+
*/
340+
@Bean(BeanNames.COUCHBASE_TRANSACTION_TEMPLATE)
341+
TransactionTemplate couchbaseTransactionTemplate(CouchbaseCallbackTransactionManager couchbaseTransactionManager) {
342+
return new TransactionTemplate(couchbaseTransactionManager);
343+
}
333344
/**
334345
* The default TransactionalOperator.
335346
*

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

+2
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,7 @@ public class BeanNames {
6464

6565
public static final String COUCHBASE_TRANSACTION_MANAGER = "couchbaseTransactionManager";
6666

67+
public static final String COUCHBASE_TRANSACTION_TEMPLATE = "couchbaseTransactionTemplate";
68+
6769
public static final String COUCHBASE_TRANSACTIONAL_OPERATOR = "couchbaseTransactionalOperator";
6870
}

src/test/java/org/springframework/data/couchbase/transactions/CouchbasePersonTransactionReactiveIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void shouldRollbackAfterException() {
108108

109109
@Test
110110
public void shouldRollbackAfterExceptionOfTxAnnotatedMethod() {
111-
assertThrowsWithCause(() -> personService.declarativeSavePersonErrors(WalterWhite).blockLast(),
111+
assertThrowsWithCause(() -> personService.declarativeSavePersonErrors(WalterWhite).block(),
112112
TransactionSystemUnambiguousException.class, SimulateFailureException.class);
113113
}
114114

src/test/java/org/springframework/data/couchbase/transactions/CouchbaseTransactionalRepositoryIntegrationTests.java

-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ public void saveRolledBack() {
114114
String id = UUID.randomUUID().toString();
115115

116116
assertThrowsWithCause(() -> {
117-
;
118117
userService.run(repo -> {
119118
User user = repo.save(new User(id, "Ada", "Lovelace"));
120119
SimulateFailureException.throwEx("fail");

0 commit comments

Comments
 (0)