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,50 @@ 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+ * 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 );
161203 }
162204
163205 /**
@@ -367,16 +409,6 @@ void appendToOptions(Options options) {
367409 }
368410 }
369411
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-
380412 /** Option to request the transaction to be excluded from change streams. */
381413 static final class ExcludeTxnFromChangeStreamsOption extends InternalOption
382414 implements UpdateTransactionOption {
@@ -516,6 +548,20 @@ void appendToOptions(Options options) {
516548 }
517549 }
518550
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+
519565 private boolean withCommitStats ;
520566
521567 private Duration maxCommitDelay ;
@@ -530,7 +576,6 @@ void appendToOptions(Options options) {
530576 private String tag ;
531577 private String etag ;
532578 private Boolean validateOnly ;
533- private Boolean withOptimisticLock ;
534579 private Boolean withExcludeTxnFromChangeStreams ;
535580 private Boolean dataBoostEnabled ;
536581 private DirectedReadOptions directedReadOptions ;
@@ -540,6 +585,7 @@ void appendToOptions(Options options) {
540585 private Boolean lastStatement ;
541586 private IsolationLevel isolationLevel ;
542587 private XGoogSpannerRequestId reqId ;
588+ private ReadLockMode readLockMode ;
543589
544590 // Construction is via factory methods below.
545591 private Options () {}
@@ -644,10 +690,6 @@ Boolean validateOnly() {
644690 return validateOnly ;
645691 }
646692
647- Boolean withOptimisticLock () {
648- return withOptimisticLock ;
649- }
650-
651693 Boolean withExcludeTxnFromChangeStreams () {
652694 return withExcludeTxnFromChangeStreams ;
653695 }
@@ -704,6 +746,10 @@ IsolationLevel isolationLevel() {
704746 return isolationLevel ;
705747 }
706748
749+ ReadLockMode readLockMode () {
750+ return readLockMode ;
751+ }
752+
707753 @ Override
708754 public String toString () {
709755 StringBuilder b = new StringBuilder ();
@@ -740,9 +786,6 @@ public String toString() {
740786 if (validateOnly != null ) {
741787 b .append ("validateOnly: " ).append (validateOnly ).append (' ' );
742788 }
743- if (withOptimisticLock != null ) {
744- b .append ("withOptimisticLock: " ).append (withOptimisticLock ).append (' ' );
745- }
746789 if (withExcludeTxnFromChangeStreams != null ) {
747790 b .append ("withExcludeTxnFromChangeStreams: " )
748791 .append (withExcludeTxnFromChangeStreams )
@@ -772,6 +815,9 @@ public String toString() {
772815 if (reqId != null ) {
773816 b .append ("requestId: " ).append (reqId .toString ());
774817 }
818+ if (readLockMode != null ) {
819+ b .append ("readLockMode: " ).append (readLockMode ).append (' ' );
820+ }
775821 return b .toString ();
776822 }
777823
@@ -807,15 +853,15 @@ public boolean equals(Object o) {
807853 && Objects .equals (tag (), that .tag ())
808854 && Objects .equals (etag (), that .etag ())
809855 && Objects .equals (validateOnly (), that .validateOnly ())
810- && Objects .equals (withOptimisticLock (), that .withOptimisticLock ())
811856 && Objects .equals (withExcludeTxnFromChangeStreams (), that .withExcludeTxnFromChangeStreams ())
812857 && Objects .equals (dataBoostEnabled (), that .dataBoostEnabled ())
813858 && Objects .equals (directedReadOptions (), that .directedReadOptions ())
814859 && Objects .equals (orderBy (), that .orderBy ())
815860 && Objects .equals (isLastStatement (), that .isLastStatement ())
816861 && Objects .equals (lockHint (), that .lockHint ())
817862 && Objects .equals (isolationLevel (), that .isolationLevel ())
818- && Objects .equals (reqId (), that .reqId ());
863+ && Objects .equals (reqId (), that .reqId ())
864+ && Objects .equals (readLockMode (), that .readLockMode ());
819865 }
820866
821867 @ Override
@@ -857,9 +903,6 @@ public int hashCode() {
857903 if (validateOnly != null ) {
858904 result = 31 * result + validateOnly .hashCode ();
859905 }
860- if (withOptimisticLock != null ) {
861- result = 31 * result + withOptimisticLock .hashCode ();
862- }
863906 if (withExcludeTxnFromChangeStreams != null ) {
864907 result = 31 * result + withExcludeTxnFromChangeStreams .hashCode ();
865908 }
@@ -887,6 +930,9 @@ public int hashCode() {
887930 if (reqId != null ) {
888931 result = 31 * result + reqId .hashCode ();
889932 }
933+ if (readLockMode != null ) {
934+ result = 31 * result + readLockMode .hashCode ();
935+ }
890936 return result ;
891937 }
892938
0 commit comments