0
# Programmatic Transaction Management
1
2
Spring's programmatic transaction management provides explicit control over transaction boundaries through template classes and direct API usage, ideal for scenarios requiring fine-grained transaction control.
3
4
## TransactionTemplate
5
6
Template class for programmatic transaction management with consistent exception handling and resource management.
7
8
```java { .api }
9
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations {
10
11
/**
12
* Construct a new TransactionTemplate using the given transaction manager.
13
* @param transactionManager the transaction manager to use
14
*/
15
public TransactionTemplate(PlatformTransactionManager transactionManager);
16
17
/**
18
* Construct a new TransactionTemplate using the given transaction manager,
19
* taking its default settings from the given transaction definition.
20
* @param transactionManager the transaction manager to use
21
* @param transactionDefinition transaction definition to copy from
22
*/
23
public TransactionTemplate(PlatformTransactionManager transactionManager,
24
TransactionDefinition transactionDefinition);
25
26
/**
27
* Set the transaction manager.
28
* @param transactionManager the transaction manager
29
*/
30
public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager);
31
32
/**
33
* Return the transaction manager.
34
* @return the transaction manager
35
*/
36
@Nullable
37
public PlatformTransactionManager getTransactionManager();
38
39
/**
40
* Execute the action specified by the given callback object within a transaction.
41
* @param action callback object that specifies the transactional action
42
* @return result object returned by the callback, or null if none
43
* @throws TransactionException in case of transaction failure
44
*/
45
@Override
46
@Nullable
47
public <T> T execute(TransactionCallback<T> action) throws TransactionException;
48
49
/**
50
* Execute the action specified by the given callback object within a transaction.
51
* @param action callback object that specifies the transactional action
52
* @throws TransactionException in case of transaction failure
53
*/
54
public void executeWithoutResult(TransactionCallbackWithoutResult action)
55
throws TransactionException;
56
}
57
```
58
59
## Transaction Operations Interface
60
61
```java { .api }
62
public interface TransactionOperations {
63
/**
64
* Execute the action specified by the given callback object within a transaction.
65
* @param action callback object that specifies the transactional action
66
* @return result object returned by the callback
67
* @throws TransactionException in case of transaction failure
68
*/
69
@Nullable
70
<T> T execute(TransactionCallback<T> action) throws TransactionException;
71
}
72
```
73
74
## Callback Interfaces
75
76
### TransactionCallback
77
78
Functional interface for transaction callbacks with return values.
79
80
```java { .api }
81
@FunctionalInterface
82
public interface TransactionCallback<T> {
83
/**
84
* Gets called by TransactionTemplate.execute within a transactional context.
85
* @param status associated transaction status
86
* @return result object, or null if none
87
*/
88
@Nullable
89
T doInTransaction(TransactionStatus status);
90
}
91
```
92
93
### TransactionCallbackWithoutResult
94
95
Abstract convenience class for transaction callbacks without return values.
96
97
```java { .api }
98
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
99
100
@Override
101
@Nullable
102
public final Object doInTransaction(TransactionStatus status) {
103
doInTransactionWithoutResult(status);
104
return null;
105
}
106
107
/**
108
* Gets called by TransactionTemplate.execute within a transactional context.
109
* @param status associated transaction status
110
*/
111
protected abstract void doInTransactionWithoutResult(TransactionStatus status);
112
}
113
```
114
115
## Usage Examples
116
117
### Basic TransactionTemplate Usage
118
119
```java
120
@Service
121
public class AccountService {
122
123
private final TransactionTemplate transactionTemplate;
124
private final AccountRepository accountRepository;
125
private final AuditRepository auditRepository;
126
127
public AccountService(PlatformTransactionManager transactionManager,
128
AccountRepository accountRepository,
129
AuditRepository auditRepository) {
130
this.transactionTemplate = new TransactionTemplate(transactionManager);
131
this.accountRepository = accountRepository;
132
this.auditRepository = auditRepository;
133
}
134
135
public Account transferMoney(Long fromId, Long toId, BigDecimal amount) {
136
return transactionTemplate.execute(status -> {
137
Account fromAccount = accountRepository.findById(fromId)
138
.orElseThrow(() -> new AccountNotFoundException(fromId));
139
Account toAccount = accountRepository.findById(toId)
140
.orElseThrow(() -> new AccountNotFoundException(toId));
141
142
if (fromAccount.getBalance().compareTo(amount) < 0) {
143
throw new InsufficientFundsException(fromId, amount);
144
}
145
146
fromAccount.debit(amount);
147
toAccount.credit(amount);
148
149
accountRepository.save(fromAccount);
150
accountRepository.save(toAccount);
151
152
// Audit the transaction
153
auditRepository.save(new AuditRecord("TRANSFER", fromId, toId, amount));
154
155
return fromAccount;
156
});
157
}
158
}
159
```
160
161
### TransactionTemplate with Custom Settings
162
163
```java
164
@Service
165
public class OrderService {
166
167
private final TransactionTemplate readOnlyTemplate;
168
private final TransactionTemplate writeTemplate;
169
private final TransactionTemplate longRunningTemplate;
170
171
public OrderService(PlatformTransactionManager transactionManager) {
172
// Read-only template for queries
173
this.readOnlyTemplate = new TransactionTemplate(transactionManager);
174
readOnlyTemplate.setReadOnly(true);
175
readOnlyTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
176
177
// Standard write template
178
this.writeTemplate = new TransactionTemplate(transactionManager);
179
writeTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
180
writeTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
181
182
// Long-running operations template
183
this.longRunningTemplate = new TransactionTemplate(transactionManager);
184
longRunningTemplate.setTimeout(300); // 5 minutes
185
longRunningTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
186
}
187
188
public List<Order> findOrdersByStatus(OrderStatus status) {
189
return readOnlyTemplate.execute(transactionStatus ->
190
orderRepository.findByStatus(status));
191
}
192
193
public Order createOrder(Order order) {
194
return writeTemplate.execute(status -> {
195
// Validate order
196
validateOrder(order);
197
198
// Save order
199
Order savedOrder = orderRepository.save(order);
200
201
// Reserve inventory
202
inventoryService.reserveItems(order.getItems());
203
204
// Send confirmation
205
notificationService.sendOrderConfirmation(savedOrder);
206
207
return savedOrder;
208
});
209
}
210
211
public void processBatchOrders(List<Order> orders) {
212
longRunningTemplate.executeWithoutResult(status -> {
213
for (Order order : orders) {
214
try {
215
processOrder(order);
216
} catch (Exception e) {
217
// Log error but continue with other orders
218
log.error("Failed to process order {}", order.getId(), e);
219
}
220
}
221
});
222
}
223
}
224
```
225
226
### Exception Handling and Rollback Control
227
228
```java
229
@Service
230
public class PaymentService {
231
232
private final TransactionTemplate transactionTemplate;
233
234
public PaymentResult processPayment(Payment payment) {
235
return transactionTemplate.execute(status -> {
236
try {
237
// Validate payment
238
validatePayment(payment);
239
240
// Charge credit card
241
ChargeResult chargeResult = creditCardService.charge(payment);
242
243
if (!chargeResult.isSuccessful()) {
244
// Business failure - rollback transaction
245
status.setRollbackOnly();
246
return PaymentResult.failed(chargeResult.getErrorMessage());
247
}
248
249
// Save payment record
250
Payment savedPayment = paymentRepository.save(payment);
251
252
// Send receipt
253
receiptService.sendReceipt(savedPayment);
254
255
return PaymentResult.successful(savedPayment);
256
257
} catch (ValidationException e) {
258
// Validation errors - rollback transaction
259
status.setRollbackOnly();
260
return PaymentResult.failed("Validation failed: " + e.getMessage());
261
262
} catch (CreditCardException e) {
263
// Card processing errors - rollback transaction
264
status.setRollbackOnly();
265
return PaymentResult.failed("Card processing failed: " + e.getMessage());
266
267
} catch (Exception e) {
268
// Unexpected errors - transaction will rollback automatically
269
log.error("Unexpected error processing payment", e);
270
throw e;
271
}
272
});
273
}
274
}
275
```
276
277
### Nested Transactions with TransactionTemplate
278
279
```java
280
@Service
281
public class ComplexOrderService {
282
283
private final TransactionTemplate outerTemplate;
284
private final TransactionTemplate innerTemplate;
285
286
public ComplexOrderService(PlatformTransactionManager transactionManager) {
287
this.outerTemplate = new TransactionTemplate(transactionManager);
288
outerTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
289
290
this.innerTemplate = new TransactionTemplate(transactionManager);
291
innerTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
292
}
293
294
public Order processComplexOrder(Order order) {
295
return outerTemplate.execute(outerStatus -> {
296
// Main order processing in outer transaction
297
Order savedOrder = orderRepository.save(order);
298
299
// Process each item in separate transactions
300
for (OrderItem item : order.getItems()) {
301
try {
302
processItemInSeparateTransaction(item);
303
} catch (ItemProcessingException e) {
304
// Item processing failures don't affect main order
305
log.warn("Failed to process item {}, continuing with order",
306
item.getId(), e);
307
item.setStatus(ItemStatus.FAILED);
308
item.setErrorMessage(e.getMessage());
309
}
310
}
311
312
// Update order with final status
313
savedOrder.updateStatus();
314
return orderRepository.save(savedOrder);
315
});
316
}
317
318
private void processItemInSeparateTransaction(OrderItem item) {
319
innerTemplate.executeWithoutResult(innerStatus -> {
320
// This runs in separate transaction
321
inventoryService.reserveItem(item);
322
shippingService.scheduleShipment(item);
323
item.setStatus(ItemStatus.PROCESSED);
324
});
325
}
326
}
327
```
328
329
### Direct PlatformTransactionManager Usage
330
331
For maximum control, you can use the PlatformTransactionManager directly:
332
333
```java
334
@Service
335
public class LowLevelTransactionService {
336
337
private final PlatformTransactionManager transactionManager;
338
339
public LowLevelTransactionService(PlatformTransactionManager transactionManager) {
340
this.transactionManager = transactionManager;
341
}
342
343
public void performComplexOperation() {
344
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
345
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
346
def.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
347
def.setTimeout(30);
348
def.setName("ComplexOperation");
349
350
TransactionStatus status = transactionManager.getTransaction(def);
351
352
try {
353
// Perform operations
354
performStep1();
355
356
// Create savepoint
357
Object savepoint = status.createSavepoint();
358
359
try {
360
performRiskyStep2();
361
} catch (RiskyOperationException e) {
362
// Rollback to savepoint
363
status.rollbackToSavepoint(savepoint);
364
performAlternativeStep2();
365
} finally {
366
// Release savepoint
367
status.releaseSavepoint(savepoint);
368
}
369
370
performStep3();
371
372
// Commit transaction
373
transactionManager.commit(status);
374
375
} catch (Exception e) {
376
// Rollback on any error
377
transactionManager.rollback(status);
378
throw e;
379
}
380
}
381
}
382
```
383
384
### Multiple Transaction Managers
385
386
```java
387
@Service
388
public class MultiDataSourceService {
389
390
private final TransactionTemplate primaryTemplate;
391
private final TransactionTemplate secondaryTemplate;
392
393
public MultiDataSourceService(
394
@Qualifier("primaryTransactionManager") PlatformTransactionManager primaryTxManager,
395
@Qualifier("secondaryTransactionManager") PlatformTransactionManager secondaryTxManager) {
396
397
this.primaryTemplate = new TransactionTemplate(primaryTxManager);
398
this.secondaryTemplate = new TransactionTemplate(secondaryTxManager);
399
}
400
401
public void synchronizeData(SyncData data) {
402
// Save to primary database
403
User user = primaryTemplate.execute(status -> {
404
User savedUser = primaryUserRepository.save(data.getUser());
405
// Other primary DB operations
406
return savedUser;
407
});
408
409
// Save to secondary database in separate transaction
410
secondaryTemplate.executeWithoutResult(status -> {
411
UserProfile profile = new UserProfile(user);
412
secondaryUserProfileRepository.save(profile);
413
// Other secondary DB operations
414
});
415
}
416
}
417
```
418
419
## Best Practices
420
421
### Error Handling Patterns
422
423
```java
424
public class TransactionBestPractices {
425
426
private final TransactionTemplate transactionTemplate;
427
428
// Pattern 1: Return success/failure indicators
429
public OperationResult performOperation(OperationData data) {
430
return transactionTemplate.execute(status -> {
431
try {
432
// Perform operation
433
performBusinessLogic(data);
434
return OperationResult.success();
435
} catch (BusinessException e) {
436
// Business logic failure - rollback
437
status.setRollbackOnly();
438
return OperationResult.failure(e.getMessage());
439
}
440
// Runtime exceptions will cause automatic rollback
441
});
442
}
443
444
// Pattern 2: Use Optional for null handling
445
public Optional<User> findAndUpdateUser(Long userId, UserUpdate update) {
446
return transactionTemplate.execute(status -> {
447
return userRepository.findById(userId)
448
.map(user -> {
449
user.apply(update);
450
return userRepository.save(user);
451
});
452
});
453
}
454
455
// Pattern 3: Collect and return multiple results
456
public BatchResult processBatch(List<BatchItem> items) {
457
return transactionTemplate.execute(status -> {
458
List<BatchItem> processed = new ArrayList<>();
459
List<String> errors = new ArrayList<>();
460
461
for (BatchItem item : items) {
462
try {
463
BatchItem result = processItem(item);
464
processed.add(result);
465
} catch (ItemProcessingException e) {
466
errors.add("Item " + item.getId() + ": " + e.getMessage());
467
}
468
}
469
470
return new BatchResult(processed, errors);
471
});
472
}
473
}
474
```