22
22
import com .google .spanner .v1 .ReadRequest .OrderBy ;
23
23
import com .google .spanner .v1 .RequestOptions .Priority ;
24
24
import com .google .spanner .v1 .TransactionOptions .IsolationLevel ;
25
+ import com .google .spanner .v1 .TransactionOptions .ReadWrite .ReadLockMode ;
25
26
import java .io .Serializable ;
26
27
import java .time .Duration ;
27
28
import java .util .Objects ;
@@ -155,9 +156,50 @@ public static TransactionOption commitStats() {
155
156
* process in the commit phase (when any needed locks are acquired). The validation process
156
157
* succeeds only if there are no conflicting committed transactions (that committed mutations to
157
158
* the read data at a commit timestamp after the read timestamp).
159
+ *
160
+ * @deprecated Use {@link Options#readLockMode(ReadLockMode)} instead.
158
161
*/
162
+ @ Deprecated
159
163
public static TransactionOption optimisticLock () {
160
- return OPTIMISTIC_LOCK_OPTION ;
164
+ return Options .readLockMode (ReadLockMode .OPTIMISTIC );
165
+ }
166
+
167
+ /**
168
+ * Returns a {@link TransactionOption} to set the desired {@link ReadLockMode} for a read-write
169
+ * transaction.
170
+ *
171
+ * <p>This option controls the locking behavior for read operations and queries within a
172
+ * read-write transaction. It works in conjunction with the transaction's {@link IsolationLevel}.
173
+ *
174
+ * <ul>
175
+ * <li>{@link ReadLockMode#PESSIMISTIC}: Read locks are acquired immediately on read. This mode
176
+ * only applies to {@code SERIALIZABLE} isolation. This mode prevents concurrent
177
+ * modifications by locking data throughout the transaction. This reduces commit-time aborts
178
+ * due to conflicts but can increase how long transactions wait for locks and the overall
179
+ * contention.
180
+ * <li>{@link ReadLockMode#OPTIMISTIC}: Locks for reads within the transaction are not acquired
181
+ * on read. Instead the locks are acquired on commit to validate that read/queried data has
182
+ * not changed since the transaction started. If a conflict is detected, the transaction
183
+ * will fail. This mode only applies to {@code SERIALIZABLE} isolation. This mode defers
184
+ * locking until commit, which can reduce contention and improve throughput. However, be
185
+ * aware that this increases the risk of transaction aborts if there's significant write
186
+ * competition on the same data.
187
+ * <li>{@link ReadLockMode#READ_LOCK_MODE_UNSPECIFIED}: This is the default if no mode is set.
188
+ * The locking behavior depends on the isolation level:
189
+ * <ul>
190
+ * <li>For {@code REPEATABLE_READ} isolation: Locking semantics default to {@code
191
+ * OPTIMISTIC}. However, validation checks at commit are only performed for queries
192
+ * using {@code SELECT FOR UPDATE}, statements with {@code LOCK_SCANNED_RANGES} hints,
193
+ * and DML statements. <br>
194
+ * Note: It is an error to explicitly set {@code ReadLockMode} when the isolation
195
+ * level is {@code REPEATABLE_READ}.
196
+ * <li>For all other isolation levels: If the read lock mode is not set, it defaults to
197
+ * {@code PESSIMISTIC} locking.
198
+ * </ul>
199
+ * </ul>
200
+ */
201
+ public static TransactionOption readLockMode (ReadLockMode readLockMode ) {
202
+ return new ReadLockModeOption (readLockMode );
161
203
}
162
204
163
205
/**
@@ -367,16 +409,6 @@ void appendToOptions(Options options) {
367
409
}
368
410
}
369
411
370
- /** Option to request Optimistic Concurrency Control for read/write transactions. */
371
- static final class OptimisticLockOption extends InternalOption implements TransactionOption {
372
- @ Override
373
- void appendToOptions (Options options ) {
374
- options .withOptimisticLock = true ;
375
- }
376
- }
377
-
378
- static final OptimisticLockOption OPTIMISTIC_LOCK_OPTION = new OptimisticLockOption ();
379
-
380
412
/** Option to request the transaction to be excluded from change streams. */
381
413
static final class ExcludeTxnFromChangeStreamsOption extends InternalOption
382
414
implements UpdateTransactionOption {
@@ -516,6 +548,20 @@ void appendToOptions(Options options) {
516
548
}
517
549
}
518
550
551
+ /** Option to set read lock mode for read/write transactions. */
552
+ static final class ReadLockModeOption extends InternalOption implements TransactionOption {
553
+ private final ReadLockMode readLockMode ;
554
+
555
+ public ReadLockModeOption (ReadLockMode readLockMode ) {
556
+ this .readLockMode = readLockMode ;
557
+ }
558
+
559
+ @ Override
560
+ void appendToOptions (Options options ) {
561
+ options .readLockMode = readLockMode ;
562
+ }
563
+ }
564
+
519
565
private boolean withCommitStats ;
520
566
521
567
private Duration maxCommitDelay ;
@@ -530,7 +576,6 @@ void appendToOptions(Options options) {
530
576
private String tag ;
531
577
private String etag ;
532
578
private Boolean validateOnly ;
533
- private Boolean withOptimisticLock ;
534
579
private Boolean withExcludeTxnFromChangeStreams ;
535
580
private Boolean dataBoostEnabled ;
536
581
private DirectedReadOptions directedReadOptions ;
@@ -540,6 +585,7 @@ void appendToOptions(Options options) {
540
585
private Boolean lastStatement ;
541
586
private IsolationLevel isolationLevel ;
542
587
private XGoogSpannerRequestId reqId ;
588
+ private ReadLockMode readLockMode ;
543
589
544
590
// Construction is via factory methods below.
545
591
private Options () {}
@@ -644,10 +690,6 @@ Boolean validateOnly() {
644
690
return validateOnly ;
645
691
}
646
692
647
- Boolean withOptimisticLock () {
648
- return withOptimisticLock ;
649
- }
650
-
651
693
Boolean withExcludeTxnFromChangeStreams () {
652
694
return withExcludeTxnFromChangeStreams ;
653
695
}
@@ -704,6 +746,10 @@ IsolationLevel isolationLevel() {
704
746
return isolationLevel ;
705
747
}
706
748
749
+ ReadLockMode readLockMode () {
750
+ return readLockMode ;
751
+ }
752
+
707
753
@ Override
708
754
public String toString () {
709
755
StringBuilder b = new StringBuilder ();
@@ -740,9 +786,6 @@ public String toString() {
740
786
if (validateOnly != null ) {
741
787
b .append ("validateOnly: " ).append (validateOnly ).append (' ' );
742
788
}
743
- if (withOptimisticLock != null ) {
744
- b .append ("withOptimisticLock: " ).append (withOptimisticLock ).append (' ' );
745
- }
746
789
if (withExcludeTxnFromChangeStreams != null ) {
747
790
b .append ("withExcludeTxnFromChangeStreams: " )
748
791
.append (withExcludeTxnFromChangeStreams )
@@ -772,6 +815,9 @@ public String toString() {
772
815
if (reqId != null ) {
773
816
b .append ("requestId: " ).append (reqId .toString ());
774
817
}
818
+ if (readLockMode != null ) {
819
+ b .append ("readLockMode: " ).append (readLockMode ).append (' ' );
820
+ }
775
821
return b .toString ();
776
822
}
777
823
@@ -807,15 +853,15 @@ public boolean equals(Object o) {
807
853
&& Objects .equals (tag (), that .tag ())
808
854
&& Objects .equals (etag (), that .etag ())
809
855
&& Objects .equals (validateOnly (), that .validateOnly ())
810
- && Objects .equals (withOptimisticLock (), that .withOptimisticLock ())
811
856
&& Objects .equals (withExcludeTxnFromChangeStreams (), that .withExcludeTxnFromChangeStreams ())
812
857
&& Objects .equals (dataBoostEnabled (), that .dataBoostEnabled ())
813
858
&& Objects .equals (directedReadOptions (), that .directedReadOptions ())
814
859
&& Objects .equals (orderBy (), that .orderBy ())
815
860
&& Objects .equals (isLastStatement (), that .isLastStatement ())
816
861
&& Objects .equals (lockHint (), that .lockHint ())
817
862
&& Objects .equals (isolationLevel (), that .isolationLevel ())
818
- && Objects .equals (reqId (), that .reqId ());
863
+ && Objects .equals (reqId (), that .reqId ())
864
+ && Objects .equals (readLockMode (), that .readLockMode ());
819
865
}
820
866
821
867
@ Override
@@ -857,9 +903,6 @@ public int hashCode() {
857
903
if (validateOnly != null ) {
858
904
result = 31 * result + validateOnly .hashCode ();
859
905
}
860
- if (withOptimisticLock != null ) {
861
- result = 31 * result + withOptimisticLock .hashCode ();
862
- }
863
906
if (withExcludeTxnFromChangeStreams != null ) {
864
907
result = 31 * result + withExcludeTxnFromChangeStreams .hashCode ();
865
908
}
@@ -887,6 +930,9 @@ public int hashCode() {
887
930
if (reqId != null ) {
888
931
result = 31 * result + reqId .hashCode ();
889
932
}
933
+ if (readLockMode != null ) {
934
+ result = 31 * result + readLockMode .hashCode ();
935
+ }
890
936
return result ;
891
937
}
892
938
0 commit comments