2222import com .google .spanner .v1 .ReadRequest .OrderBy ;
2323import com .google .spanner .v1 .RequestOptions .Priority ;
2424import com .google .spanner .v1 .TransactionOptions .IsolationLevel ;
25+ import com .google .spanner .v1 .TransactionOptions .ReadWrite .ReadLockMode ;
2526import java .io .Serializable ;
2627import java .time .Duration ;
2728import java .util .Objects ;
@@ -155,9 +156,44 @@ public static TransactionOption commitStats() {
155156 * process in the commit phase (when any needed locks are acquired). The validation process
156157 * succeeds only if there are no conflicting committed transactions (that committed mutations to
157158 * the read data at a commit timestamp after the read timestamp).
159+ *
160+ * @deprecated Use {@link Options#readLockMode(ReadLockMode)} instead.
158161 */
162+ @ Deprecated
159163 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+ * primarily applies to transactions with {@code SERIALIZABLE} isolation.
177+ * <li>{@link ReadLockMode#OPTIMISTIC}: Locks for reads within the transaction are not acquired
178+ * on read. Instead the locks are acquired on a commit to validate that read/queried data
179+ * has not changed since the transaction started. If a conflict is detected, the transaction
180+ * will fail. This mode applies to transactions with {@code SERIALIZABLE} isolation.
181+ * <li>{@link ReadLockMode#READ_LOCK_MODE_UNSPECIFIED}: This is the default if no mode is set.
182+ * The locking behavior depends on the isolation level:
183+ * <ul>
184+ * <li>For {@code REPEATABLE_READ} isolation: Locking semantics default to {@code
185+ * OPTIMISTIC}. However, validation checks at commit are only performed for reads
186+ * within queries using {@code SELECT FOR UPDATE}, statements with {@code
187+ * LOCK_SCANNED_RANGES} hints, or DML statements. <br>
188+ * Note: It is an error to explicitly set {@code ReadLockMode} when the isolation
189+ * level is {@code REPEATABLE_READ}.
190+ * <li>For all other isolation levels: If the read lock mode is not set, it defaults to
191+ * {@code PESSIMISTIC} locking.
192+ * </ul>
193+ * </ul>
194+ */
195+ public static TransactionOption readLockMode (ReadLockMode readLockMode ) {
196+ return new ReadLockModeOption (readLockMode );
161197 }
162198
163199 /**
@@ -367,16 +403,6 @@ void appendToOptions(Options options) {
367403 }
368404 }
369405
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-
380406 /** Option to request the transaction to be excluded from change streams. */
381407 static final class ExcludeTxnFromChangeStreamsOption extends InternalOption
382408 implements UpdateTransactionOption {
@@ -516,6 +542,20 @@ void appendToOptions(Options options) {
516542 }
517543 }
518544
545+ /** Option to set read lock mode for read/write transactions. */
546+ static final class ReadLockModeOption extends InternalOption implements TransactionOption {
547+ private final ReadLockMode readLockMode ;
548+
549+ public ReadLockModeOption (ReadLockMode readLockMode ) {
550+ this .readLockMode = readLockMode ;
551+ }
552+
553+ @ Override
554+ void appendToOptions (Options options ) {
555+ options .readLockMode = readLockMode ;
556+ }
557+ }
558+
519559 private boolean withCommitStats ;
520560
521561 private Duration maxCommitDelay ;
@@ -530,7 +570,6 @@ void appendToOptions(Options options) {
530570 private String tag ;
531571 private String etag ;
532572 private Boolean validateOnly ;
533- private Boolean withOptimisticLock ;
534573 private Boolean withExcludeTxnFromChangeStreams ;
535574 private Boolean dataBoostEnabled ;
536575 private DirectedReadOptions directedReadOptions ;
@@ -540,6 +579,7 @@ void appendToOptions(Options options) {
540579 private Boolean lastStatement ;
541580 private IsolationLevel isolationLevel ;
542581 private XGoogSpannerRequestId reqId ;
582+ private ReadLockMode readLockMode ;
543583
544584 // Construction is via factory methods below.
545585 private Options () {}
@@ -644,10 +684,6 @@ Boolean validateOnly() {
644684 return validateOnly ;
645685 }
646686
647- Boolean withOptimisticLock () {
648- return withOptimisticLock ;
649- }
650-
651687 Boolean withExcludeTxnFromChangeStreams () {
652688 return withExcludeTxnFromChangeStreams ;
653689 }
@@ -704,6 +740,10 @@ IsolationLevel isolationLevel() {
704740 return isolationLevel ;
705741 }
706742
743+ ReadLockMode readLockMode () {
744+ return readLockMode ;
745+ }
746+
707747 @ Override
708748 public String toString () {
709749 StringBuilder b = new StringBuilder ();
@@ -740,9 +780,6 @@ public String toString() {
740780 if (validateOnly != null ) {
741781 b .append ("validateOnly: " ).append (validateOnly ).append (' ' );
742782 }
743- if (withOptimisticLock != null ) {
744- b .append ("withOptimisticLock: " ).append (withOptimisticLock ).append (' ' );
745- }
746783 if (withExcludeTxnFromChangeStreams != null ) {
747784 b .append ("withExcludeTxnFromChangeStreams: " )
748785 .append (withExcludeTxnFromChangeStreams )
@@ -772,6 +809,9 @@ public String toString() {
772809 if (reqId != null ) {
773810 b .append ("requestId: " ).append (reqId .toString ());
774811 }
812+ if (readLockMode != null ) {
813+ b .append ("readLockMode: " ).append (readLockMode ).append (' ' );
814+ }
775815 return b .toString ();
776816 }
777817
@@ -807,15 +847,15 @@ public boolean equals(Object o) {
807847 && Objects .equals (tag (), that .tag ())
808848 && Objects .equals (etag (), that .etag ())
809849 && Objects .equals (validateOnly (), that .validateOnly ())
810- && Objects .equals (withOptimisticLock (), that .withOptimisticLock ())
811850 && Objects .equals (withExcludeTxnFromChangeStreams (), that .withExcludeTxnFromChangeStreams ())
812851 && Objects .equals (dataBoostEnabled (), that .dataBoostEnabled ())
813852 && Objects .equals (directedReadOptions (), that .directedReadOptions ())
814853 && Objects .equals (orderBy (), that .orderBy ())
815854 && Objects .equals (isLastStatement (), that .isLastStatement ())
816855 && Objects .equals (lockHint (), that .lockHint ())
817856 && Objects .equals (isolationLevel (), that .isolationLevel ())
818- && Objects .equals (reqId (), that .reqId ());
857+ && Objects .equals (reqId (), that .reqId ())
858+ && Objects .equals (readLockMode (), that .readLockMode ());
819859 }
820860
821861 @ Override
@@ -857,9 +897,6 @@ public int hashCode() {
857897 if (validateOnly != null ) {
858898 result = 31 * result + validateOnly .hashCode ();
859899 }
860- if (withOptimisticLock != null ) {
861- result = 31 * result + withOptimisticLock .hashCode ();
862- }
863900 if (withExcludeTxnFromChangeStreams != null ) {
864901 result = 31 * result + withExcludeTxnFromChangeStreams .hashCode ();
865902 }
@@ -887,6 +924,9 @@ public int hashCode() {
887924 if (reqId != null ) {
888925 result = 31 * result + reqId .hashCode ();
889926 }
927+ if (readLockMode != null ) {
928+ result = 31 * result + readLockMode .hashCode ();
929+ }
890930 return result ;
891931 }
892932
0 commit comments