Skip to content

Commit 94744ee

Browse files
committed
JAVA-2815: Add support for transactions to the synchronous API
A few notes on the design and implementation: * It introduces a small but significant breaking change to the existing API for any applications that already depend on session support (introduced in the 3.6 release to support causal consistency): the type of ClientSession changes from com.mongodb.session.ClientSession to com.mongodb.client.ClientSession. This is both source and binary incompatible. * It adds an overload to MongoClient.startSession that takes no ClientSessionOptions, as we expect it to be a common pattern to not need any options. * Internally, it moves ReadConcern from a property of ReadOperation implementation classes to a property of the SessionContext, so that ReadConcern can be applied properly when in a transaction (transactions require that read concern is specified only on the first command of a transaction, regardless of whether it's a read or a write
1 parent ee3f346 commit 94744ee

File tree

145 files changed

+10517
-923
lines changed

Some content is hidden

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

145 files changed

+10517
-923
lines changed

config/findbugs-exclude.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,9 @@
132132
<Bug pattern="NP_BOOLEAN_RETURN_NULL"/>
133133
</Match>
134134

135+
<Match>
136+
<Class name="~com.mongodb.client.ClientSession.*"/>
137+
<Bug pattern="NM_SAME_SIMPLE_NAME_AS_INTERFACE"/>
138+
</Match>
139+
135140
</FindBugsFilter>

driver-async/src/main/com/mongodb/async/client/ClientSessionHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import com.mongodb.connection.ClusterDescription;
2323
import com.mongodb.connection.Server;
2424
import com.mongodb.connection.ServerDescription;
25-
import com.mongodb.internal.session.ClientSessionImpl;
25+
import com.mongodb.internal.session.BaseClientSessionImpl;
2626
import com.mongodb.internal.session.ServerSessionPool;
2727
import com.mongodb.lang.Nullable;
2828
import com.mongodb.selector.ServerSelector;
@@ -81,8 +81,8 @@ public void onResult(final Server server, final Throwable t) {
8181
}
8282
}
8383

84-
private ClientSessionImpl createClientSession(final ClientSessionOptions options) {
85-
return new ClientSessionImpl(serverSessionPool, mongoClient, options);
84+
private ClientSession createClientSession(final ClientSessionOptions options) {
85+
return new BaseClientSessionImpl(serverSessionPool, mongoClient, options);
8686
}
8787

8888
@SuppressWarnings("deprecation")

driver-core/src/main/com/mongodb/ClientSessionOptions.java

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.mongodb.lang.Nullable;
2222
import com.mongodb.session.ClientSession;
2323

24+
import static com.mongodb.assertions.Assertions.notNull;
25+
2426
/**
2527
* The options to apply to a {@code ClientSession}.
2628
*
@@ -33,11 +35,13 @@
3335
public final class ClientSessionOptions {
3436

3537
private final Boolean causallyConsistent;
38+
private final boolean autoStartTransaction;
39+
private final TransactionOptions defaultTransactionOptions;
3640

3741
/**
3842
* Whether operations using the session should causally consistent with each other.
3943
*
40-
* @return whether operations using the session should be causally consistent. A null value indicates to use the the global default,
44+
* @return whether operations using the session should be causally consistent. A null value indicates to use the global default,
4145
* which is currently true.
4246
* @mongodb.driver.dochub core/causal-consistency Causal Consistency
4347
*/
@@ -46,6 +50,70 @@ public Boolean isCausallyConsistent() {
4650
return causallyConsistent;
4751
}
4852

53+
/**
54+
* Gets whether a transaction should be automatically started along with the session.
55+
*
56+
* @return whether a transaction should be automatically started along with the session. The default is false.
57+
* @since 3.8
58+
* @mongodb.server.release 4.0
59+
*/
60+
public boolean getAutoStartTransaction() {
61+
return autoStartTransaction;
62+
}
63+
64+
/**
65+
* Gets the default transaction options for the session.
66+
*
67+
* @return the default transaction options for the session
68+
* @since 3.8
69+
* @mongodb.server.release 4.0
70+
*/
71+
public TransactionOptions getDefaultTransactionOptions() {
72+
return defaultTransactionOptions;
73+
}
74+
75+
@Override
76+
public boolean equals(final Object o) {
77+
if (this == o) {
78+
return true;
79+
}
80+
if (o == null || getClass() != o.getClass()) {
81+
return false;
82+
}
83+
84+
ClientSessionOptions that = (ClientSessionOptions) o;
85+
86+
if (autoStartTransaction != that.autoStartTransaction) {
87+
return false;
88+
}
89+
if (causallyConsistent != null ? !causallyConsistent.equals(that.causallyConsistent) : that.causallyConsistent != null) {
90+
return false;
91+
}
92+
if (defaultTransactionOptions != null ? !defaultTransactionOptions.equals(that.defaultTransactionOptions)
93+
: that.defaultTransactionOptions != null) {
94+
return false;
95+
}
96+
97+
return true;
98+
}
99+
100+
@Override
101+
public int hashCode() {
102+
int result = causallyConsistent != null ? causallyConsistent.hashCode() : 0;
103+
result = 31 * result + (autoStartTransaction ? 1 : 0);
104+
result = 31 * result + (defaultTransactionOptions != null ? defaultTransactionOptions.hashCode() : 0);
105+
return result;
106+
}
107+
108+
@Override
109+
public String toString() {
110+
return "ClientSessionOptions{"
111+
+ "causallyConsistent=" + causallyConsistent
112+
+ ", autoStartTransaction=" + autoStartTransaction
113+
+ ", defaultTransactionOptions=" + defaultTransactionOptions
114+
+ '}';
115+
}
116+
49117
/**
50118
* Gets an instance of a builder
51119
*
@@ -55,12 +123,30 @@ public static Builder builder() {
55123
return new Builder();
56124
}
57125

126+
/**
127+
* Gets an instance of a builder initialized with the given options
128+
*
129+
* @param options the options with which to initialize the builder
130+
* @return a builder instance
131+
* @since 3.8
132+
*/
133+
public static Builder builder(final ClientSessionOptions options) {
134+
notNull("options", options);
135+
Builder builder = new Builder();
136+
builder.causallyConsistent = options.isCausallyConsistent();
137+
builder.autoStartTransaction = options.getAutoStartTransaction();
138+
builder.defaultTransactionOptions = options.getDefaultTransactionOptions();
139+
return builder;
140+
}
141+
58142
/**
59143
* A builder for instances of {@code ClientSession}
60144
*/
61145
@NotThreadSafe
62146
public static final class Builder {
63147
private Boolean causallyConsistent;
148+
private boolean autoStartTransaction;
149+
private TransactionOptions defaultTransactionOptions = TransactionOptions.builder().build();
64150

65151
/**
66152
* Sets whether operations using the session should causally consistent with each other.
@@ -74,6 +160,33 @@ public Builder causallyConsistent(final boolean causallyConsistent) {
74160
return this;
75161
}
76162

163+
/**
164+
* Sets whether operations using the session should causally consistent with each other.
165+
*
166+
* @param autoStartTransaction whether a transaction should be started automatically when the session is started.
167+
*
168+
* @return this
169+
* @since 3.8
170+
* @mongodb.server.release 4.0
171+
*/
172+
public Builder autoStartTransaction(final boolean autoStartTransaction) {
173+
this.autoStartTransaction = autoStartTransaction;
174+
return this;
175+
}
176+
177+
/**
178+
* Sets whether operations using the session should causally consistent with each other.
179+
*
180+
* @param defaultTransactionOptions the default transaction options to use for all transactions on this session,
181+
* @return this
182+
* @since 3.8
183+
* @mongodb.server.release 4.0
184+
*/
185+
public Builder defaultTransactionOptions(final TransactionOptions defaultTransactionOptions) {
186+
this.defaultTransactionOptions = notNull("defaultTransactionOptions", defaultTransactionOptions);
187+
return this;
188+
}
189+
77190
/**
78191
* Build the session options instance.
79192
*
@@ -89,5 +202,7 @@ private Builder() {
89202

90203
private ClientSessionOptions(final Builder builder) {
91204
this.causallyConsistent = builder.causallyConsistent;
205+
this.autoStartTransaction = builder.autoStartTransaction;
206+
this.defaultTransactionOptions = builder.defaultTransactionOptions;
92207
}
93208
}

driver-core/src/main/com/mongodb/ReadConcern.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ public ReadConcern(final ReadConcernLevel level) {
6868
*/
6969
public static final ReadConcern LINEARIZABLE = new ReadConcern(ReadConcernLevel.LINEARIZABLE);
7070

71+
/**
72+
* The snapshot read concern.
73+
*
74+
* @since 3.8
75+
* @mongodb.server.release 4.0
76+
*/
77+
public static final ReadConcern SNAPSHOT = new ReadConcern(ReadConcernLevel.SNAPSHOT);
78+
7179
/**
7280
* Gets the read concern level.
7381
*

driver-core/src/main/com/mongodb/ReadConcernLevel.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ public enum ReadConcernLevel {
4848
* @since 3.4
4949
* @mongodb.server.release 3.4
5050
*/
51-
LINEARIZABLE("linearizable");
51+
LINEARIZABLE("linearizable"),
52+
53+
/**
54+
* The snapshot read concern level.
55+
*
56+
* @since 3.8
57+
* @mongodb.server.release 4.0
58+
*/
59+
SNAPSHOT("snapshot");
5260

5361
private final String value;
5462

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
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+
* http://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+
17+
package com.mongodb;
18+
19+
import com.mongodb.annotations.Immutable;
20+
import com.mongodb.lang.Nullable;
21+
22+
import static com.mongodb.assertions.Assertions.isTrueArgument;
23+
import static com.mongodb.assertions.Assertions.notNull;
24+
25+
/**
26+
* Options to apply to transactions.
27+
*
28+
* @see com.mongodb.session.ClientSession
29+
* @see ClientSessionOptions
30+
* @since 3.8
31+
* @mongodb.server.release 4.0
32+
*/
33+
@Immutable
34+
public final class TransactionOptions {
35+
private final ReadConcern readConcern;
36+
private final WriteConcern writeConcern;
37+
38+
/**
39+
* Gets the read concern.
40+
*
41+
* @return the read concern, which defaults to {@link ReadConcern#SNAPSHOT}
42+
*/
43+
@Nullable
44+
public ReadConcern getReadConcern() {
45+
return readConcern;
46+
}
47+
48+
/**
49+
* Gets the write concern.
50+
*
51+
* @return the write concern, which defaults to {@link WriteConcern#ACKNOWLEDGED}
52+
*/
53+
@Nullable
54+
public WriteConcern getWriteConcern() {
55+
return writeConcern;
56+
}
57+
58+
/**
59+
* Gets an instance of a builder
60+
*
61+
* @return a builder instance
62+
*/
63+
public static Builder builder() {
64+
return new Builder();
65+
}
66+
67+
/**
68+
* Merge the two provided transaction options, with the first taking precedence over the second.
69+
*
70+
* @param options the transaction options, which take precedence for any property that is non-null
71+
* @param defaultOptions the default transaction options
72+
* @return the merged transaction options
73+
*/
74+
public static TransactionOptions merge(final TransactionOptions options, final TransactionOptions defaultOptions) {
75+
notNull("options", options);
76+
notNull("defaultOptions", defaultOptions);
77+
return TransactionOptions.builder()
78+
.writeConcern(options.getWriteConcern() == null
79+
? defaultOptions.getWriteConcern() : options.getWriteConcern())
80+
.readConcern(options.getReadConcern() == null
81+
? defaultOptions.getReadConcern() : options.getReadConcern())
82+
.build();
83+
}
84+
85+
@Override
86+
public boolean equals(final Object o) {
87+
if (this == o) {
88+
return true;
89+
}
90+
if (o == null || getClass() != o.getClass()) {
91+
return false;
92+
}
93+
94+
TransactionOptions that = (TransactionOptions) o;
95+
96+
if (readConcern != null ? !readConcern.equals(that.readConcern) : that.readConcern != null) {
97+
return false;
98+
}
99+
if (writeConcern != null ? !writeConcern.equals(that.writeConcern) : that.writeConcern != null) {
100+
return false;
101+
}
102+
103+
return true;
104+
}
105+
106+
@Override
107+
public int hashCode() {
108+
int result = readConcern != null ? readConcern.hashCode() : 0;
109+
result = 31 * result + (writeConcern != null ? writeConcern.hashCode() : 0);
110+
return result;
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return "TransactionOptions{readConcern="
116+
+ readConcern + ", writeConcern="
117+
+ writeConcern + '}';
118+
}
119+
120+
/**
121+
* The builder for transaction options
122+
*/
123+
public static final class Builder {
124+
private ReadConcern readConcern;
125+
private WriteConcern writeConcern;
126+
127+
/**
128+
* Sets the read concern.
129+
*
130+
* @param readConcern the read concern
131+
* @return this
132+
*/
133+
public Builder readConcern(@Nullable final ReadConcern readConcern) {
134+
this.readConcern = readConcern;
135+
return this;
136+
}
137+
138+
/**
139+
* Sets the write concern.
140+
*
141+
* @param writeConcern the write concern, which must be acknowledged
142+
* @return this
143+
*/
144+
public Builder writeConcern(@Nullable final WriteConcern writeConcern) {
145+
this.writeConcern = writeConcern;
146+
isTrueArgument("acknowledged write concern", writeConcern != null && writeConcern.isAcknowledged());
147+
return this;
148+
}
149+
150+
/**
151+
* Build the transaction options instance.
152+
*
153+
* @return The {@code TransactionOptions}
154+
*/
155+
public TransactionOptions build() {
156+
return new TransactionOptions(this);
157+
}
158+
159+
private Builder() {
160+
}
161+
}
162+
163+
164+
private TransactionOptions(final Builder builder) {
165+
readConcern = builder.readConcern;
166+
writeConcern = builder.writeConcern;
167+
}
168+
}

0 commit comments

Comments
 (0)