Skip to content

Commit 0ac117f

Browse files
committed
Explicit notes on isolation level handling in participating transactions
Issue: SPR-16463
1 parent 817a836 commit 0ac117f

File tree

5 files changed

+116
-43
lines changed

5 files changed

+116
-43
lines changed

spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -205,21 +205,29 @@ public interface TransactionDefinition {
205205

206206
/**
207207
* Return the isolation level.
208-
* <p>Must return one of the {@code ISOLATION_XXX} constants
209-
* defined on {@link TransactionDefinition this interface}.
210-
* <p>Only makes sense in combination with {@link #PROPAGATION_REQUIRED}
211-
* or {@link #PROPAGATION_REQUIRES_NEW}.
208+
* <p>Must return one of the {@code ISOLATION_XXX} constants defined on
209+
* {@link TransactionDefinition this interface}. Those constants are designed
210+
* to match the values of the same constants on {@link java.sql.Connection}.
211+
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
212+
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
213+
* transactions. Consider switching the "validateExistingTransactions" flag to
214+
* "true" on your transaction manager if you'd like isolation level declarations
215+
* to get rejected when participating in an existing transaction with a different
216+
* isolation level.
212217
* <p>Note that a transaction manager that does not support custom isolation levels
213218
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
214219
* @return the isolation level
220+
* @see #ISOLATION_DEFAULT
221+
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
215222
*/
216223
int getIsolationLevel();
217224

218225
/**
219226
* Return the transaction timeout.
220227
* <p>Must return a number of seconds, or {@link #TIMEOUT_DEFAULT}.
221-
* <p>Only makes sense in combination with {@link #PROPAGATION_REQUIRED}
222-
* or {@link #PROPAGATION_REQUIRES_NEW}.
228+
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
229+
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
230+
* transactions.
223231
* <p>Note that a transaction manager that does not support timeouts will throw
224232
* an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
225233
* @return the transaction timeout
@@ -228,13 +236,12 @@ public interface TransactionDefinition {
228236

229237
/**
230238
* Return whether to optimize as a read-only transaction.
231-
* <p>The read-only flag applies to any transaction context, whether
232-
* backed by an actual resource transaction
233-
* ({@link #PROPAGATION_REQUIRED}/{@link #PROPAGATION_REQUIRES_NEW}) or
234-
* operating non-transactionally at the resource level
235-
* ({@link #PROPAGATION_SUPPORTS}). In the latter case, the flag will
236-
* only apply to managed resources within the application, such as a
237-
* Hibernate {@code Session}.
239+
* <p>The read-only flag applies to any transaction context, whether backed
240+
* by an actual resource transaction ({@link #PROPAGATION_REQUIRED}/
241+
* {@link #PROPAGATION_REQUIRES_NEW}) or operating non-transactionally at
242+
* the resource level ({@link #PROPAGATION_SUPPORTS}). In the latter case,
243+
* the flag will only apply to managed resources within the application,
244+
* such as a Hibernate {@code Session}.
238245
* <p>This just serves as a hint for the actual transaction subsystem;
239246
* it will <i>not necessarily</i> cause failure of write access attempts.
240247
* A transaction manager which cannot interpret the read-only hint will

spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,26 +86,38 @@
8686
/**
8787
* The transaction isolation level.
8888
* <p>Defaults to {@link Isolation#DEFAULT}.
89+
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
90+
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
91+
* transactions. Consider switching the "validateExistingTransactions" flag to
92+
* "true" on your transaction manager if you'd like isolation level declarations
93+
* to get rejected when participating in an existing transaction with a different
94+
* isolation level.
8995
* @see org.springframework.transaction.interceptor.TransactionAttribute#getIsolationLevel()
96+
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
9097
*/
9198
Isolation isolation() default Isolation.DEFAULT;
9299

93100
/**
94101
* The timeout for this transaction.
95102
* <p>Defaults to the default timeout of the underlying transaction system.
103+
* <p>Exclusively designed for use with {@link Propagation#REQUIRED} or
104+
* {@link Propagation#REQUIRES_NEW} since it only applies to newly started
105+
* transactions.
96106
* @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout()
97107
*/
98108
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
99109

100110
/**
101-
* {@code true} if the transaction is read-only.
111+
* A boolean flag that can be set to {@code true} if the transaction is
112+
* effectively read-only, allowing for corresponding optimizations at runtime.
102113
* <p>Defaults to {@code false}.
103114
* <p>This just serves as a hint for the actual transaction subsystem;
104115
* it will <i>not necessarily</i> cause failure of write access attempts.
105116
* A transaction manager which cannot interpret the read-only hint will
106117
* <i>not</i> throw an exception when asked for a read-only transaction
107118
* but rather silently ignore the hint.
108119
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
120+
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
109121
*/
110122
boolean readOnly() default false;
111123

spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -215,6 +215,7 @@ public final boolean isNestedTransactionAllowed() {
215215
* <p>Default is "false", leniently ignoring inner transaction settings,
216216
* simply overriding them with the outer transaction's characteristics.
217217
* Switch this flag to "true" in order to enforce strict validation.
218+
* @since 2.5.1
218219
*/
219220
public final void setValidateExistingTransaction(boolean validateExistingTransaction) {
220221
this.validateExistingTransaction = validateExistingTransaction;
@@ -223,6 +224,7 @@ public final void setValidateExistingTransaction(boolean validateExistingTransac
223224
/**
224225
* Return whether existing transactions should be validated before participating
225226
* in them.
227+
* @since 2.5.1
226228
*/
227229
public final boolean isValidateExistingTransaction() {
228230
return this.validateExistingTransaction;
@@ -285,6 +287,7 @@ public final boolean isGlobalRollbackOnParticipationFailure() {
285287
* boundary. This allows, for example, to continue unit tests even after an
286288
* operation failed and the transaction will never be completed. All transaction
287289
* managers will only fail earlier if this flag has explicitly been set to "true".
290+
* @since 2.0
288291
* @see org.springframework.transaction.UnexpectedRollbackException
289292
*/
290293
public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRollbackOnly) {
@@ -294,6 +297,7 @@ public final void setFailEarlyOnGlobalRollbackOnly(boolean failEarlyOnGlobalRoll
294297
/**
295298
* Return whether to fail early in case of the transaction being globally marked
296299
* as rollback-only.
300+
* @since 2.0
297301
*/
298302
public final boolean isFailEarlyOnGlobalRollbackOnly() {
299303
return this.failEarlyOnGlobalRollbackOnly;

spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionDefinition.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -110,7 +110,7 @@ public DefaultTransactionDefinition(int propagationBehavior) {
110110
* Set the propagation behavior by the name of the corresponding constant in
111111
* TransactionDefinition, e.g. "PROPAGATION_REQUIRED".
112112
* @param constantName name of the constant
113-
* @exception IllegalArgumentException if the supplied value is not resolvable
113+
* @throws IllegalArgumentException if the supplied value is not resolvable
114114
* to one of the {@code PROPAGATION_} constants or is {@code null}
115115
* @see #setPropagationBehavior
116116
* @see #PROPAGATION_REQUIRED
@@ -125,8 +125,16 @@ public final void setPropagationBehaviorName(String constantName) throws Illegal
125125
/**
126126
* Set the propagation behavior. Must be one of the propagation constants
127127
* in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED.
128-
* @exception IllegalArgumentException if the supplied value is not
129-
* one of the {@code PROPAGATION_} constants
128+
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
129+
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
130+
* transactions. Consider switching the "validateExistingTransactions" flag to
131+
* "true" on your transaction manager if you'd like isolation level declarations
132+
* to get rejected when participating in an existing transaction with a different
133+
* isolation level.
134+
* <p>Note that a transaction manager that does not support custom isolation levels
135+
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
136+
* @throws IllegalArgumentException if the supplied value is not one of the
137+
* {@code PROPAGATION_} constants
130138
* @see #PROPAGATION_REQUIRED
131139
*/
132140
public final void setPropagationBehavior(int propagationBehavior) {
@@ -145,7 +153,7 @@ public final int getPropagationBehavior() {
145153
* Set the isolation level by the name of the corresponding constant in
146154
* TransactionDefinition, e.g. "ISOLATION_DEFAULT".
147155
* @param constantName name of the constant
148-
* @exception IllegalArgumentException if the supplied value is not resolvable
156+
* @throws IllegalArgumentException if the supplied value is not resolvable
149157
* to one of the {@code ISOLATION_} constants or is {@code null}
150158
* @see #setIsolationLevel
151159
* @see #ISOLATION_DEFAULT
@@ -160,8 +168,16 @@ public final void setIsolationLevelName(String constantName) throws IllegalArgum
160168
/**
161169
* Set the isolation level. Must be one of the isolation constants
162170
* in the TransactionDefinition interface. Default is ISOLATION_DEFAULT.
163-
* @exception IllegalArgumentException if the supplied value is not
164-
* one of the {@code ISOLATION_} constants
171+
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
172+
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
173+
* transactions. Consider switching the "validateExistingTransactions" flag to
174+
* "true" on your transaction manager if you'd like isolation level declarations
175+
* to get rejected when participating in an existing transaction with a different
176+
* isolation level.
177+
* <p>Note that a transaction manager that does not support custom isolation levels
178+
* will throw an exception when given any other level than {@link #ISOLATION_DEFAULT}.
179+
* @throws IllegalArgumentException if the supplied value is not one of the
180+
* {@code ISOLATION_} constants
165181
* @see #ISOLATION_DEFAULT
166182
*/
167183
public final void setIsolationLevel(int isolationLevel) {
@@ -179,6 +195,11 @@ public final int getIsolationLevel() {
179195
/**
180196
* Set the timeout to apply, as number of seconds.
181197
* Default is TIMEOUT_DEFAULT (-1).
198+
* <p>Exclusively designed for use with {@link #PROPAGATION_REQUIRED} or
199+
* {@link #PROPAGATION_REQUIRES_NEW} since it only applies to newly started
200+
* transactions.
201+
* <p>Note that a transaction manager that does not support timeouts will throw
202+
* an exception when given any other timeout than {@link #TIMEOUT_DEFAULT}.
182203
* @see #TIMEOUT_DEFAULT
183204
*/
184205
public final void setTimeout(int timeout) {
@@ -196,6 +217,16 @@ public final int getTimeout() {
196217
/**
197218
* Set whether to optimize as read-only transaction.
198219
* Default is "false".
220+
* <p>The read-only flag applies to any transaction context, whether backed
221+
* by an actual resource transaction ({@link #PROPAGATION_REQUIRED}/
222+
* {@link #PROPAGATION_REQUIRES_NEW}) or operating non-transactionally at
223+
* the resource level ({@link #PROPAGATION_SUPPORTS}). In the latter case,
224+
* the flag will only apply to managed resources within the application,
225+
* such as a Hibernate {@code Session}.
226+
* <p>This just serves as a hint for the actual transaction subsystem;
227+
* it will <i>not necessarily</i> cause failure of write access attempts.
228+
* A transaction manager which cannot interpret the read-only hint will
229+
* <i>not</i> throw an exception when asked for a read-only transaction.
199230
*/
200231
public final void setReadOnly(boolean readOnly) {
201232
this.readOnly = readOnly;

src/docs/asciidoc/data-access.adoc

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,16 @@ execution.
195195

196196
The `TransactionDefinition` interface specifies:
197197

198-
* __Isolation__: The degree to which this transaction is isolated from the work of other
199-
transactions. For example, can this transaction see uncommitted writes from other
200-
transactions?
201198
* __Propagation__: Typically, all code executed within a transaction scope will run in
202199
that transaction. However, you have the option of specifying the behavior in the event
203200
that a transactional method is executed when a transaction context already exists. For
204201
example, code can continue running in the existing transaction (the common case); or
205202
the existing transaction can be suspended and a new transaction created. __Spring
206203
offers all of the transaction propagation options familiar from EJB CMT__. To read
207204
about the semantics of transaction propagation in Spring, see <<tx-propagation>>.
205+
* __Isolation__: The degree to which this transaction is isolated from the work of other
206+
transactions. For example, can this transaction see uncommitted writes from other
207+
transactions?
208208
* __Timeout__: How long this transaction runs before timing out and being rolled back
209209
automatically by the underlying transaction infrastructure.
210210
* __Read-only status__: A read-only transaction can be used when your code reads but
@@ -1021,17 +1021,17 @@ that are nested within `<tx:advice/>` and `<tx:attributes/>` tags are summarized
10211021
| `isolation`
10221022
| No
10231023
| DEFAULT
1024-
| Transaction isolation level.
1024+
| Transaction isolation level. Only applicable to propagation REQUIRED or REQUIRES_NEW.
10251025

10261026
| `timeout`
10271027
| No
10281028
| -1
1029-
| Transaction timeout value (in seconds).
1029+
| Transaction timeout (seconds). Only applicable to propagation REQUIRED or REQUIRES_NEW.
10301030

10311031
| `read-only`
10321032
| No
10331033
| false
1034-
| Is this transaction read-only?
1034+
| Read/write vs. read-only transaction. Only applicable to REQUIRED or REQUIRES_NEW.
10351035

10361036
| `rollback-for`
10371037
| No
@@ -1155,7 +1155,6 @@ transactional behavior.
11551155

11561156
[TIP]
11571157
====
1158-
11591158
Spring recommends that you only annotate concrete classes (and methods of concrete
11601159
classes) with the `@Transactional` annotation, as opposed to annotating interfaces. You
11611160
certainly can place the `@Transactional` annotation on an interface (or an interface
@@ -1301,23 +1300,23 @@ annotation are summarized in the following table:
13011300

13021301
| <<tx-multiple-tx-mgrs-with-attransactional,value>>
13031302
| String
1304-
| Optional qualifier specifying the transaction manager to be used.
1303+
| Optional qualifier specifying the transaction manager to be used.
13051304

13061305
| <<tx-propagation,propagation>>
13071306
| enum: `Propagation`
13081307
| Optional propagation setting.
13091308

13101309
| `isolation`
13111310
| enum: `Isolation`
1312-
| Optional isolation level.
1313-
1314-
| `readOnly`
1315-
| boolean
1316-
| Read/write vs. read-only transaction
1311+
| Optional isolation level. Only applicable to propagation REQUIRED or REQUIRES_NEW.
13171312

13181313
| `timeout`
13191314
| int (in seconds granularity)
1320-
| Transaction timeout.
1315+
| Optional transaction timeout. Only applicable to propagation REQUIRED or REQUIRES_NEW.
1316+
1317+
| `readOnly`
1318+
| boolean
1319+
| Read/write vs. read-only transaction. Only applicable to REQUIRED or REQUIRES_NEW.
13211320

13221321
| `rollbackFor`
13231322
| Array of `Class` objects, which must be derived from `Throwable.`
@@ -1451,11 +1450,28 @@ image::images/tx_prop_required.png[]
14511450

14521451
PROPAGATION_REQUIRED
14531452

1453+
`PROPAGATION_REQUIRED` enforces a physical transaction: either locally for the current
1454+
scope if no transaction exists yet, or participating in an existing 'outer' transaction
1455+
defined for a larger scope. This is a fine default in common call stack arrangements
1456+
within the same thread, e.g. a service facade delegating to several repository methods
1457+
where all the underlying resources have to participate in the service-level transaction.
1458+
1459+
[NOTE]
1460+
====
1461+
By default, a participating transaction will join the characteristics of the outer scope,
1462+
silently ignoring the local isolation level, timeout value or read-only flag (if any).
1463+
Consider switching the "validateExistingTransactions" flag to "true" on your transaction
1464+
manager if you'd like isolation level declarations to get rejected when participating in
1465+
an existing transaction with a different isolation level. This non-lenient mode will also
1466+
reject read-only mismatches, i.e. an inner read-write transaction trying to participate
1467+
in a read-only outer scope.
1468+
====
1469+
14541470
When the propagation setting is `PROPAGATION_REQUIRED`, a __logical__ transaction scope
14551471
is created for each method upon which the setting is applied. Each such logical
14561472
transaction scope can determine rollback-only status individually, with an outer
1457-
transaction scope being logically independent from the inner transaction scope. Of
1458-
course, in case of standard `PROPAGATION_REQUIRED` behavior, all these scopes will be
1473+
transaction scope being logically independent from the inner transaction scope.
1474+
Of course, in case of standard `PROPAGATION_REQUIRED` behavior, all these scopes will be
14591475
mapped to the same physical transaction. So a rollback-only marker set in the inner
14601476
transaction scope does affect the outer transaction's chance to actually commit (as you
14611477
would expect it to).
@@ -1477,11 +1493,14 @@ image::images/tx_prop_requires_new.png[]
14771493

14781494
PROPAGATION_REQUIRES_NEW
14791495

1480-
`PROPAGATION_REQUIRES_NEW`, in contrast to `PROPAGATION_REQUIRED`, uses a __completely__
1481-
independent transaction for each affected transaction scope. In that case, the
1482-
underlying physical transactions are different and hence can commit or roll back
1496+
`PROPAGATION_REQUIRES_NEW`, in contrast to `PROPAGATION_REQUIRED`, always uses an
1497+
__independent__ physical transaction for each affected transaction scope, never
1498+
participating in an existing transaction for an outer scope. In such an arrangement,
1499+
the underlying resource transactions are different and hence can commit or roll back
14831500
independently, with an outer transaction not affected by an inner transaction's rollback
1484-
status.
1501+
status, and with an inner transaction's locks released immediately after its completion.
1502+
Such an independent inner transaction may also declare its own isolation level, timeout
1503+
and read-only settings, never inheriting an outer transaction's characteristics.
14851504

14861505
[[tx-propagation-nested]]
14871506
===== Nested

0 commit comments

Comments
 (0)