0
# Declarative Transaction Management
1
2
Standard JTA @Transactional annotations with Quarkus-specific enhancements for timeout configuration and comprehensive CDI integration.
3
4
## Capabilities
5
6
### @Transactional Annotation
7
8
Standard Jakarta Transaction annotation for declarative transaction management.
9
10
```java { .api }
11
/**
12
* Marks method or class for automatic transaction management
13
*/
14
@Target({ElementType.TYPE, ElementType.METHOD})
15
@Retention(RetentionPolicy.RUNTIME)
16
@Inherited
17
@interface Transactional {
18
19
/**
20
* Transaction propagation behavior
21
* @return TxType constant defining transaction semantics
22
*/
23
TxType value() default TxType.REQUIRED;
24
25
/**
26
* Exception types that trigger rollback
27
* @return Array of exception classes
28
*/
29
Class[] rollbackOn() default {};
30
31
/**
32
* Exception types that do NOT trigger rollback
33
* @return Array of exception classes
34
*/
35
Class[] dontRollbackOn() default {};
36
}
37
```
38
39
**TxType Values:**
40
41
```java { .api }
42
enum TxType {
43
/** Join existing transaction or create new one (default) */
44
REQUIRED,
45
46
/** Always create new transaction, suspend existing */
47
REQUIRES_NEW,
48
49
/** Must run within existing transaction, throw exception if none */
50
MANDATORY,
51
52
/** Run within transaction if exists, without transaction if none */
53
SUPPORTS,
54
55
/** Always run without transaction, suspend existing */
56
NOT_SUPPORTED,
57
58
/** Throw exception if transaction exists */
59
NEVER
60
}
61
```
62
63
**Usage Examples:**
64
65
```java
66
import jakarta.transaction.Transactional;
67
import static jakarta.transaction.Transactional.TxType.*;
68
69
@ApplicationScoped
70
public class UserService {
71
72
@Transactional // Uses REQUIRED by default
73
public void createUser(User user) {
74
validateUser(user);
75
userRepository.persist(user);
76
// Transaction committed automatically on success
77
// Rolled back automatically on RuntimeException
78
}
79
80
@Transactional(REQUIRES_NEW)
81
public void auditUserCreation(String username) {
82
// Always runs in new transaction, independent of caller
83
auditRepository.log("User created: " + username);
84
}
85
86
@Transactional(MANDATORY)
87
public void updateUserInTransaction(User user) {
88
// Must be called within existing transaction
89
user.setLastModified(Instant.now());
90
userRepository.merge(user);
91
}
92
93
@Transactional(NOT_SUPPORTED)
94
public void sendEmailNotification(String email, String message) {
95
// Runs outside transaction scope
96
emailService.send(email, message);
97
}
98
}
99
```
100
101
### Exception Handling in Declarative Transactions
102
103
Configure which exceptions cause rollback vs commit.
104
105
```java { .api }
106
/**
107
* Control transaction rollback based on exception types
108
*/
109
@Transactional(
110
rollbackOn = {BusinessException.class, ValidationException.class},
111
dontRollbackOn = {WarningException.class}
112
)
113
```
114
115
**Usage Examples:**
116
117
```java
118
@ApplicationScoped
119
public class PaymentService {
120
121
// Roll back on any RuntimeException (default behavior)
122
@Transactional
123
public void processPayment(Payment payment) {
124
if (payment.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
125
throw new IllegalArgumentException("Invalid amount"); // Triggers rollback
126
}
127
paymentProcessor.charge(payment);
128
}
129
130
// Custom rollback behavior
131
@Transactional(
132
rollbackOn = {InsufficientFundsException.class},
133
dontRollbackOn = {PaymentWarningException.class}
134
)
135
public void processRiskyPayment(Payment payment) {
136
try {
137
paymentProcessor.processHighRisk(payment);
138
} catch (PaymentWarningException e) {
139
// Transaction continues and commits
140
logger.warn("Payment processed with warning", e);
141
} catch (InsufficientFundsException e) {
142
// Transaction is rolled back
143
throw e;
144
}
145
}
146
}
147
```
148
149
### @TransactionConfiguration Annotation
150
151
Quarkus-specific annotation for configuring transaction timeouts.
152
153
```java { .api }
154
/**
155
* Configure transaction timeout at method or class level
156
*/
157
@Target({ElementType.METHOD, ElementType.TYPE})
158
@Retention(RetentionPolicy.RUNTIME)
159
@Inherited
160
@interface TransactionConfiguration {
161
162
/** Indicates no timeout configured */
163
int UNSET_TIMEOUT = -1;
164
165
/** Indicates no config property configured */
166
String UNSET_TIMEOUT_CONFIG_PROPERTY = "<<unset>>";
167
168
/**
169
* Transaction timeout in seconds
170
* @return Timeout value, UNSET_TIMEOUT for default
171
*/
172
int timeout() default UNSET_TIMEOUT;
173
174
/**
175
* Configuration property name for timeout value
176
* Property value takes precedence over timeout() if both are set
177
* @return Property name or UNSET_TIMEOUT_CONFIG_PROPERTY
178
*/
179
String timeoutFromConfigProperty() default UNSET_TIMEOUT_CONFIG_PROPERTY;
180
}
181
```
182
183
**Usage Examples:**
184
185
```java
186
@ApplicationScoped
187
public class BatchProcessingService {
188
189
@Transactional
190
@TransactionConfiguration(timeout = 300) // 5 minutes
191
public void processLargeBatch(List<BatchItem> items) {
192
for (BatchItem item : items) {
193
processItem(item);
194
}
195
}
196
197
@Transactional
198
@TransactionConfiguration(timeoutFromConfigProperty = "batch.processing.timeout")
199
public void configurableBatchProcess(List<BatchItem> items) {
200
// Timeout read from application.properties: batch.processing.timeout=600
201
processBatchItems(items);
202
}
203
204
@Transactional
205
@TransactionConfiguration(
206
timeout = 120, // Fallback value
207
timeoutFromConfigProperty = "critical.operation.timeout"
208
)
209
public void criticalOperation() {
210
// Uses property value if set, otherwise uses 120 seconds
211
performCriticalWork();
212
}
213
}
214
```
215
216
### Class-Level Configuration
217
218
Apply transaction behavior to all methods in a class.
219
220
```java { .api }
221
/**
222
* Class-level annotations apply to all @Transactional methods
223
* Method-level configuration overrides class-level
224
*/
225
@Transactional(REQUIRES_NEW)
226
@TransactionConfiguration(timeout = 60)
227
@ApplicationScoped
228
public class AuditService {
229
230
// Inherits REQUIRES_NEW and 60s timeout
231
@Transactional
232
public void logUserAction(String action) { }
233
234
// Overrides to use REQUIRED semantics, keeps 60s timeout
235
@Transactional(REQUIRED)
236
public void logSystemEvent(String event) { }
237
238
// Overrides timeout to 30s, keeps REQUIRES_NEW semantics
239
@TransactionConfiguration(timeout = 30)
240
@Transactional
241
public void logQuickEvent(String event) { }
242
}
243
```
244
245
### CDI Integration Patterns
246
247
Integration with CDI scopes and lifecycle events.
248
249
```java { .api }
250
/**
251
* Transaction-scoped CDI beans
252
*/
253
@TransactionScoped
254
public class TransactionScopedAuditLogger {
255
256
private List<String> transactionLogs = new ArrayList<>();
257
258
@PostConstruct
259
void onTransactionBegin() {
260
// Called when transaction begins
261
transactionLogs.add("Transaction started at " + Instant.now());
262
}
263
264
@PreDestroy
265
void onTransactionEnd() {
266
// Called before transaction ends (commit or rollback)
267
persistLogs(transactionLogs);
268
}
269
270
public void log(String message) {
271
transactionLogs.add(message);
272
}
273
}
274
```
275
276
**Transaction Lifecycle Events:**
277
278
```java
279
@ApplicationScoped
280
public class TransactionEventObserver {
281
282
void onTransactionBegin(@Observes @Initialized(TransactionScoped.class) Object event) {
283
logger.info("Transaction scope initialized");
284
}
285
286
void onBeforeTransactionEnd(@Observes @BeforeDestroyed(TransactionScoped.class) Object event) {
287
logger.info("Transaction about to end");
288
}
289
290
void onTransactionEnd(@Observes @Destroyed(TransactionScoped.class) Object event) {
291
logger.info("Transaction scope destroyed");
292
}
293
}
294
```
295
296
### Interceptor Behavior
297
298
Understanding how transaction interceptors work with method calls.
299
300
```java
301
@ApplicationScoped
302
public class OrderService {
303
304
@Transactional
305
public void processOrder(Order order) {
306
validateOrder(order); // Runs in same transaction
307
persistOrder(order); // Runs in same transaction
308
notifyCustomer(order); // Runs in same transaction
309
}
310
311
@Transactional(REQUIRES_NEW)
312
private void auditOrderProcessing(Order order) {
313
// Private method - interceptor NOT applied!
314
// This will NOT start a new transaction
315
auditRepository.log("Processing order: " + order.getId());
316
}
317
318
@Transactional(REQUIRES_NEW)
319
public void auditOrderProcessingPublic(Order order) {
320
// Public method - interceptor applied
321
// This WILL start a new transaction
322
auditRepository.log("Processing order: " + order.getId());
323
}
324
}
325
```
326
327
## Best Practices
328
329
### Method Visibility
330
331
```java
332
// ✅ Correct - public method, interceptor applies
333
@Transactional
334
public void processData() { }
335
336
// ❌ Wrong - private method, interceptor NOT applied
337
@Transactional
338
private void processDataPrivate() { }
339
340
// ✅ Correct - package-private works with CDI
341
@Transactional
342
void processDataPackage() { }
343
```
344
345
### Self-Invocation Issues
346
347
```java
348
@ApplicationScoped
349
public class DocumentService {
350
351
@Transactional
352
public void processDocument(Document doc) {
353
validateDocument(doc);
354
// ❌ Self-invocation - transaction interceptor NOT applied
355
this.saveDocument(doc);
356
}
357
358
@Transactional(REQUIRES_NEW)
359
public void saveDocument(Document doc) {
360
// This will NOT start new transaction when called from processDocument
361
documentRepository.save(doc);
362
}
363
}
364
365
// ✅ Solution: Inject self or use separate service
366
@ApplicationScoped
367
public class DocumentService {
368
369
@Inject
370
DocumentService self; // CDI proxy
371
372
@Transactional
373
public void processDocument(Document doc) {
374
validateDocument(doc);
375
// ✅ Correct - uses CDI proxy, interceptor applies
376
self.saveDocument(doc);
377
}
378
}
379
```
380
381
### Exception Handling Best Practices
382
383
```java
384
@Transactional
385
public void businessOperation() {
386
try {
387
riskyOperation();
388
} catch (CheckedException e) {
389
// Checked exceptions don't trigger rollback by default
390
// Convert to runtime exception to trigger rollback
391
throw new BusinessException("Operation failed", e);
392
}
393
}
394
395
@Transactional(rollbackOn = CheckedException.class)
396
public void businessOperationWithCheckedRollback() {
397
// Now CheckedException will trigger rollback
398
riskyOperationThatThrowsCheckedException();
399
}
400
```