Skip to content

Commit 57af87e

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

11 files changed

+279
-167
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

+138-63
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,189 @@
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

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).
12+
== Overview
13+
The Spring Data Couchbase template operations insert, find, replace and delete and repository methods that use those calls can participate in a Couchbase Transaction. They can be executed in a transaction by using the @Transactional annotation, the CouchbaseTransactionalOperator, or in the lambda of a Couchbase Transaction.
1514

16-
- Group: `com.couchbase.client`
17-
- Artifact: `couchbase-transactions`
18-
- Version: latest one, i.e. `1.0.0`
15+
== Getting Started & Configuration
1916

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:
17+
Couchbase Transactions are normally leveraged with a method annotated with @Transactional.
18+
The @Transactional operator is implemented with the CouchbaseTransactionManager which is supplied as a bean in the AbstractCouchbaseConfiguration.
19+
Couchbase Transactions can be used without defining a service class by using CouchbaseTransactionOperator which is also supplied as a bean in AbtractCouchbaseConfiguration.
20+
Couchbase Transactions can also be used directly using Spring Data Couchbase operations within a lambda https://docs.couchbase.com/server/current/learn/data/transactions.html#using-transactions[Using Transactions]
2221

2322
.Transaction Configuration
2423
====
2524
[source,java]
2625
----
2726
@Configuration
27+
@EnableCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
28+
@EnableReactiveCouchbaseRepositories("<parent-dir-of-repository-interfaces>")
29+
@EnableTransactionManagement <1>
2830
static class Config extends AbstractCouchbaseConfiguration {
2931
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-
32+
// Usual Setup
33+
@Override public String getConnectionString() { /* ... */ }
34+
@Override public String getUserName() { /* ... */ }
35+
@Override public String getPassword() { /* ... */ }
36+
@Override public String getBucketName() { /* ... */ }
37+
38+
// Customization of transaction behavior is via the configureEnvironment() method
39+
@Override protected void configureEnvironment(final Builder builder) {
40+
builder.transactionsConfig(
41+
TransactionsConfig.builder().timeout(Duration.ofSeconds(30)));
42+
}
4343
}
4444
----
4545
====
4646

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:
47+
48+
Functioning of the @Transactional method annotation requires
49+
[start=1]
50+
. the configuration class to be annotated with @EnableTransactionManagement;
51+
. the service object with the annotated methods must be annotated with @Service and @Component;
52+
. the service object with the annotated methods must be obtained as an @Bean either via @Autowire or
53+
from the application context;
54+
. the call to the method must be made from a different class than service because calling an annotated
55+
method from the same class will not invoke the Method Interceptor that does the transaction processing.
56+
57+
== Transactions with @Transactional
58+
59+
@Transactional defines as transactional a method or all methods on a class.
60+
61+
When this annotation is declared at the class level, it applies as a default
62+
to all methods of the declaring class and its subclasses.
63+
64+
=== Attribute Semantics
65+
66+
In this release, the Couchbase Transactions ignores the rollback attributes.
67+
The transaction isoloation level is read committed;
68+
69+
=== Transaction Management
70+
71+
Blocking transactions are thread-bound and therefore do not propagate to newly started threads within the method.
72+
Non-blocking transactions are bound to the reactive context.
5173

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

71-
== Object Conversions
133+
== Transactions with CouchbaseTransactionalOperator
72134

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:
135+
CouchbaseTransactionalOperator can be used to construct a tansaction in-line without creating a service class that uses @Transactional.
136+
CouchbaseTransactionalOperator is available as a bean and can be instantiated with @Autowire.
137+
If creating one explicitly, it must be created with the following (and not TransactionalOperator.create(manager) as described in general Spring Data transaction documenation).
76138

77-
.Transaction Conversion on Write
139+
.TransactionalOperator Construction
78140
====
79141
[source,java]
80142
----
81-
@Autowired
82-
MappingCouchbaseConverter mappingCouchbaseConverter;
143+
public static CouchbaseTransactionalOperator create(CouchbaseCallbackTransactionManager manager)
144+
====
83145

84-
public void doSomething() {
85-
transactions.run(ctx -> {
146+
.Transaction Access Using TransactionalOperator.execute()
147+
====
148+
[source,java]
149+
----
150+
@Autowired TransactionalOperator txOperator;
151+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
86152
87-
Airline airline = new Airline("demo-airline", "at");
88-
CouchbaseDocument target = new CouchbaseDocument();
89-
mappingCouchbaseConverter.write(airline, target);
153+
Person person = reactiveCouchbaseTemplate.insertById(Person.class).inCollection(cName).one(WalterWhite).block();
154+
Flux<Person> result = txOperator.execute((ctx) -> reactiveCouchbaseTemplate.findById(Person.class).one(person.id())
155+
.flatMap(p -> reactiveCouchbaseTemplate.replaceById(Person.class).one(p.withFirstName("Walt"))));
156+
result.blockLast();
157+
----
158+
====
90159

91-
ctx.insert(couchbaseClientFactory.getDefaultCollection(), target.getId(), target.getContent());
160+
== Transactions Directly with the SDK
92161

93-
ctx.commit();
94-
});
95-
}
96-
----
162+
Please see the https://docs.couchbase.com/java-sdk/current/howtos/distributed-acid-transactions-from-the-sdk.html[Reference Documentation]
163+
164+
.Transaction Access - Blocking
97165
====
166+
[source,java]
167+
----
168+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
98169
99-
The same approach can be used on read:
170+
TransactionResult result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().transactions().run(ctx -> {
171+
return reactiveCouchbaseTemplate.insertById(Person.class).one(WalterWhite);
172+
});
173+
----
174+
====
100175

101-
.Transaction Conversion on Read
176+
.Transaction Access - Reactive
102177
====
103178
[source,java]
104179
----
105-
TransactionGetResult getResult = ctx.get(couchbaseClientFactory.getDefaultCollection(), "doc-id");
180+
@Autowired ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
106181
107-
CouchbaseDocument source = new CouchbaseDocument(getResult.id());
108-
source.setContent(getResult.contentAsObject());
109-
Airline read = mappingCouchbaseConverter.read(Airline.class, source);
182+
TransactionResult result = reactiveCouchbaseTemplate.getCouchbaseClientFactory().getCluster().reactive().transactions()
183+
.run(ctx -> TransactionalSupport.checkForTransactionInThreadLocalStorage().then(Mono.defer(() -> {
184+
reactiveCouchbaseTemplate.insertById(Person.class).one(WalterWhite);
185+
}))).block();
110186
----
111187
====
112188

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

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)