Programmatic transaction management provides explicit control over transaction boundaries through template classes and callback interfaces. Use this approach when you need fine-grained control over transaction demarcation or when working with code that cannot use declarative annotations.
Template class for programmatic transaction demarcation. Simplifies transaction management by handling transaction lifecycle and exception translation.
/**
* Template class for executing code within transactions programmatically.
* Simplifies transaction management with template/callback pattern.
*/
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
/**
* Construct a new TransactionTemplate for bean usage.
*/
public TransactionTemplate();
/**
* Construct a new TransactionTemplate with the given transaction manager.
*/
public TransactionTemplate(PlatformTransactionManager transactionManager);
/**
* Construct a new TransactionTemplate with the given transaction manager
* and a custom TransactionDefinition.
*/
public TransactionTemplate(
PlatformTransactionManager transactionManager,
TransactionDefinition transactionDefinition
);
/**
* Set the transaction manager for this template.
*/
public void setTransactionManager(PlatformTransactionManager transactionManager);
/**
* Get the transaction manager for this template.
*/
public PlatformTransactionManager getTransactionManager();
/**
* Execute the action specified by the callback within a transaction.
* Returns the result returned by the callback, or null.
*/
public <T> T execute(TransactionCallback<T> action) throws TransactionException;
/**
* Execute the action without expecting a result.
*/
public void executeWithoutResult(Consumer<TransactionStatus> action)
throws TransactionException;
}Usage Examples:
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.PlatformTransactionManager;
@Service
public class PaymentService {
private final TransactionTemplate transactionTemplate;
private final PaymentRepository paymentRepository;
// Constructor injection
public PaymentService(PlatformTransactionManager transactionManager,
PaymentRepository paymentRepository) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.paymentRepository = paymentRepository;
}
// Execute with result
public Payment processPayment(PaymentRequest request) {
return transactionTemplate.execute(status -> {
Payment payment = new Payment(request);
paymentRepository.save(payment);
notificationService.sendConfirmation(payment);
return payment;
});
}
// Execute without result
public void recordPayment(Payment payment) {
transactionTemplate.executeWithoutResult(status -> {
paymentRepository.save(payment);
auditLog.record("Payment processed: " + payment.getId());
});
}
// Custom transaction settings
public void criticalOperation() {
TransactionTemplate template = new TransactionTemplate(transactionManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
template.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
template.setTimeout(30);
template.executeWithoutResult(status -> {
// Execute critical operation
performCriticalWork();
});
}
// Manual rollback
public void processWithManualRollback(Order order) {
transactionTemplate.executeWithoutResult(status -> {
try {
orderRepository.save(order);
paymentService.charge(order);
} catch (PaymentException e) {
// Explicitly mark for rollback
status.setRollbackOnly();
logger.error("Payment failed, rolling back", e);
}
});
}
// Read-only transaction
public List<Payment> getPaymentHistory(Long accountId) {
TransactionTemplate readOnlyTemplate = new TransactionTemplate(transactionManager);
readOnlyTemplate.setReadOnly(true);
return readOnlyTemplate.execute(status ->
paymentRepository.findByAccountId(accountId)
);
}
}Callback interface for transactional code with a return value.
/**
* Callback interface for transactional code.
* Used with TransactionTemplate to execute code within a transaction.
*
* @param <T> the result type
*/
@FunctionalInterface
public interface TransactionCallback<T> {
/**
* Execute within a transaction.
* Allows the implementation to perform any number of operations on a
* single transaction. Throw unchecked exceptions to trigger rollback.
*
* @param status transaction status providing control and inspection capabilities
* @return a result object or null
*/
T doInTransaction(TransactionStatus status);
}Usage Examples:
// Lambda expression (most common)
transactionTemplate.execute(status -> {
User user = userRepository.save(new User());
return user;
});
// Explicit implementation
TransactionCallback<Order> callback = new TransactionCallback<Order>() {
@Override
public Order doInTransaction(TransactionStatus status) {
Order order = new Order();
orderRepository.save(order);
return order;
}
};
Order result = transactionTemplate.execute(callback);
// Method reference
public Order createOrder(TransactionStatus status) {
Order order = new Order();
orderRepository.save(order);
return order;
}
Order result = transactionTemplate.execute(this::createOrder);Abstract convenience class for transactional code without a return value.
/**
* Convenience class for TransactionCallback implementation without result.
* Simplifies transaction template usage when no result is needed.
*/
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
/**
* Final implementation of TransactionCallback.doInTransaction.
* Delegates to doInTransactionWithoutResult.
*/
@Override
public final Object doInTransaction(TransactionStatus status);
/**
* Execute transactional code without returning a result.
*
* @param status transaction status
*/
protected abstract void doInTransactionWithoutResult(TransactionStatus status);
}Usage Example:
// Using TransactionCallbackWithoutResult
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
orderRepository.save(order);
inventoryService.updateStock(order);
}
});
// Preferred modern approach using Consumer
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(order);
inventoryService.updateStock(order);
});Interface defining basic transaction execution operations. TransactionTemplate implements this interface.
/**
* Interface specifying basic transaction execution operations.
* Implemented by TransactionTemplate.
*/
public interface TransactionOperations {
/**
* Execute the action specified by the callback within a transaction.
*
* @param action callback object that specifies transactional action
* @return result object returned by the callback, or null
* @throws TransactionException in case of transaction errors
*/
<T> T execute(TransactionCallback<T> action) throws TransactionException;
/**
* Execute the action without expecting a result.
*
* @param action consumer that specifies transactional action
* @throws TransactionException in case of transaction errors
*/
default void executeWithoutResult(Consumer<TransactionStatus> action)
throws TransactionException;
/**
* Return a TransactionOperations instance that executes callbacks
* without actual transaction management.
*/
static TransactionOperations withoutTransaction();
}Usage Example:
@Service
public class OrderService {
private final TransactionOperations transactionOperations;
// Inject TransactionOperations (allows testing with mock)
public OrderService(TransactionOperations transactionOperations) {
this.transactionOperations = transactionOperations;
}
public Order createOrder(OrderRequest request) {
return transactionOperations.execute(status -> {
Order order = new Order(request);
orderRepository.save(order);
return order;
});
}
}
// Testing with no-op transactions
@Test
public void testWithoutTransaction() {
TransactionOperations noOpTx = TransactionOperations.withoutTransaction();
OrderService service = new OrderService(noOpTx);
// Test without actual transaction management
}Representation of transaction status with control and query methods.
/**
* Representation of the status of a transaction.
* Provides control methods and status query methods.
*/
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
/**
* Return whether this transaction internally carries a savepoint.
*/
boolean hasSavepoint();
/**
* Flush the underlying session to the datastore, if applicable.
*/
void flush();
}
/**
* Common representation of current transaction execution state.
*/
public interface TransactionExecution {
/**
* Return the name of the current transaction, if any.
*/
String getTransactionName();
/**
* Return whether there is an actual transaction active.
*/
boolean hasTransaction();
/**
* Return whether the transaction is new, otherwise participating in existing.
*/
boolean isNewTransaction();
/**
* Return whether the transaction is nested within another.
*/
boolean isNested();
/**
* Return if this transaction is read-only.
*/
boolean isReadOnly();
/**
* Set the transaction rollback-only.
*/
void setRollbackOnly();
/**
* Return whether the transaction has been marked as rollback-only.
*/
boolean isRollbackOnly();
/**
* Return whether the transaction is completed (committed or rolled back).
*/
boolean isCompleted();
}
/**
* Interface for managing transaction savepoints.
*/
public interface SavepointManager {
/**
* Create a new savepoint.
*/
Object createSavepoint() throws TransactionException;
/**
* Roll back to the given savepoint.
*/
void rollbackToSavepoint(Object savepoint) throws TransactionException;
/**
* Release the given savepoint.
*/
void releaseSavepoint(Object savepoint) throws TransactionException;
}Usage Examples:
@Service
public class TransactionStatusExamples {
private final TransactionTemplate transactionTemplate;
public void manualRollback() {
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(order);
if (inventoryService.checkStock(order) < 0) {
// Explicitly mark transaction for rollback
status.setRollbackOnly();
logger.warn("Insufficient stock, rolling back");
return;
}
inventoryService.reduceStock(order);
});
}
public void checkTransactionStatus() {
transactionTemplate.execute(status -> {
// Query transaction status
boolean isNew = status.isNewTransaction();
boolean isReadOnly = status.isReadOnly();
boolean isRollbackOnly = status.isRollbackOnly();
logger.info("Transaction - new: {}, readOnly: {}, rollbackOnly: {}",
isNew, isReadOnly, isRollbackOnly);
// Perform work
performWork();
return null;
});
}
public void useSavepoints() {
transactionTemplate.executeWithoutResult(status -> {
// Main operation
orderRepository.save(order);
// Create savepoint before risky operation
Object savepoint = status.createSavepoint();
try {
// Attempt risky operation
riskyService.performOperation();
status.releaseSavepoint(savepoint);
} catch (Exception e) {
// Rollback to savepoint, continue transaction
status.rollbackToSavepoint(savepoint);
logger.warn("Rolled back to savepoint", e);
}
// Continue with transaction
notificationService.send(order);
});
}
public void flushChanges() {
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(order);
// Force flush to database
status.flush();
// Continue with more operations
processOrder(order);
});
}
}Mutable implementation of TransactionDefinition interface with default settings.
/**
* Default implementation of TransactionDefinition interface.
* Offers bean-style configuration and defaults.
*/
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
/**
* Create a new DefaultTransactionDefinition with default settings.
*/
public DefaultTransactionDefinition();
/**
* Copy constructor. Create a new DefaultTransactionDefinition with the
* same settings as the given definition.
*/
public DefaultTransactionDefinition(TransactionDefinition other);
/**
* Create a new DefaultTransactionDefinition with the given propagation behavior.
*/
public DefaultTransactionDefinition(int propagationBehavior);
/**
* Set the propagation behavior.
*/
public void setPropagationBehavior(int propagationBehavior);
/**
* Set the isolation level.
*/
public void setIsolationLevel(int isolationLevel);
/**
* Set the timeout in seconds.
*/
public void setTimeout(int timeout);
/**
* Set whether to optimize as read-only transaction.
*/
public void setReadOnly(boolean readOnly);
/**
* Set the name of the transaction.
*/
public void setName(String name);
}Usage Example:
@Service
public class CustomTransactionService {
private final PlatformTransactionManager transactionManager;
public void customTransaction() {
// Create custom transaction definition
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
def.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
def.setTimeout(30);
def.setReadOnly(false);
def.setName("CustomTransaction");
// Create template with custom definition
TransactionTemplate template = new TransactionTemplate(transactionManager, def);
template.executeWithoutResult(status -> {
// Execute with custom transaction settings
performCriticalOperation();
});
}
}@Service
public class OrderProcessingService {
private final TransactionTemplate transactionTemplate;
private final TransactionTemplate readOnlyTemplate;
public OrderProcessingService(PlatformTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.readOnlyTemplate = new TransactionTemplate(transactionManager);
this.readOnlyTemplate.setReadOnly(true);
}
public void processOrder(Long orderId) {
// Read order (read-only transaction)
Order order = readOnlyTemplate.execute(status ->
orderRepository.findById(orderId)
);
// Process order (write transaction)
transactionTemplate.executeWithoutResult(status -> {
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order);
inventoryService.reserveItems(order);
});
// Log audit (independent transaction)
TransactionTemplate auditTemplate = new TransactionTemplate(transactionManager);
auditTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
auditTemplate.executeWithoutResult(status -> {
auditLog.record("Order processed: " + orderId);
});
}
}@Service
public class ConditionalRollbackService {
private final TransactionTemplate transactionTemplate;
public void processWithConditionalRollback(Order order) {
transactionTemplate.executeWithoutResult(status -> {
orderRepository.save(order);
ValidationResult result = validator.validate(order);
if (!result.isValid()) {
status.setRollbackOnly();
logger.warn("Validation failed: {}", result.getErrors());
return;
}
inventoryService.updateStock(order);
});
}
}@Service
public class SavepointService {
private final TransactionTemplate transactionTemplate;
public void processWithSavepoints(List<Order> orders) {
transactionTemplate.executeWithoutResult(status -> {
for (Order order : orders) {
Object savepoint = status.createSavepoint();
try {
processOrder(order);
status.releaseSavepoint(savepoint);
} catch (Exception e) {
// Rollback this order only, continue with others
status.rollbackToSavepoint(savepoint);
logger.error("Failed to process order: " + order.getId(), e);
}
}
});
}
}@Service
public class MixedTransactionService {
private final TransactionTemplate transactionTemplate;
// Declarative transaction for standard operations
@Transactional
public void standardOperation(Order order) {
orderRepository.save(order);
}
// Programmatic transaction for complex logic
public void complexOperation(List<Order> orders) {
for (Order order : orders) {
transactionTemplate.executeWithoutResult(status -> {
try {
processOrder(order);
} catch (NonCriticalException e) {
// Log but don't rollback
logger.warn("Non-critical error", e);
} catch (CriticalException e) {
status.setRollbackOnly();
throw e;
}
});
}
}
}Bean scope implementation for transaction-scoped beans. Beans in this scope live for the duration of a transaction.
/**
* Simple transaction-backed Scope implementation.
* Binds beans to the current transaction.
*/
public class SimpleTransactionScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory);
@Override
public Object remove(String name);
@Override
public void registerDestructionCallback(String name, Runnable callback);
@Override
public Object resolveContextualObject(String key);
@Override
public String getConversationId();
}Usage Example:
import org.springframework.transaction.support.SimpleTransactionScope;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
@Configuration
public class TransactionScopeConfig {
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("transaction", new SimpleTransactionScope());
return configurer;
}
@Bean
@Scope("transaction")
public TransactionResource transactionResource() {
return new TransactionResource();
}
}Singleton TransactionOperations implementation for testing scenarios where you want to execute code without transactions.
/**
* TransactionOperations implementation that executes without actual transactions.
* Useful for testing.
*/
public class WithoutTransactionOperations implements TransactionOperations {
/**
* Return the singleton instance.
*/
public static TransactionOperations withoutTransaction();
@Override
public <T> T execute(TransactionCallback<T> action);
@Override
public void executeWithoutResult(Consumer<TransactionStatus> action);
}Usage Example:
import org.springframework.transaction.support.WithoutTransactionOperations;
// For testing without transactions
TransactionOperations noOpTransactionOps =
WithoutTransactionOperations.withoutTransaction();
noOpTransactionOps.executeWithoutResult(status -> {
// Code runs without transaction
repository.save(entity);
});Abstract base class for TransactionStatus implementations. Provides common functionality for tracking rollback and completion state.
/**
* Abstract base class for TransactionStatus implementations.
*/
public abstract class AbstractTransactionStatus implements TransactionStatus {
private boolean rollbackOnly = false;
private boolean completed = false;
private Object savepoint;
// From TransactionExecution
public abstract String getTransactionName();
public abstract boolean hasTransaction();
public abstract boolean isNewTransaction();
public abstract boolean isNested();
public abstract boolean isReadOnly();
@Override
public void setRollbackOnly();
@Override
public boolean isRollbackOnly();
/**
* Determine if rollback-only was set locally.
*/
public boolean isLocalRollbackOnly();
/**
* Determine if rollback-only was set globally.
*/
public boolean isGlobalRollbackOnly();
@Override
public boolean isCompleted();
// From SavepointManager
@Override
public Object createSavepoint();
@Override
public void rollbackToSavepoint(Object savepoint);
@Override
public void releaseSavepoint(Object savepoint);
@Override
public boolean hasSavepoint();
// From Flushable
@Override
public void flush();
/**
* Set completion state.
*/
public void setCompleted();
}Simple TransactionStatus implementation for use with TransactionCallbackWithoutResult or testing scenarios.
/**
* Simple concrete implementation of TransactionStatus.
* Pre-set status information.
*/
public class SimpleTransactionStatus extends AbstractTransactionStatus {
private final boolean newTransaction;
/**
* Create new instance with default settings (new transaction).
*/
public SimpleTransactionStatus();
/**
* Create new instance with specified new transaction flag.
*/
public SimpleTransactionStatus(boolean newTransaction);
@Override
public boolean isNewTransaction();
@Override
public boolean hasTransaction();
@Override
public String getTransactionName();
@Override
public boolean isNested();
@Override
public boolean isReadOnly();
}Usage Example:
import org.springframework.transaction.support.SimpleTransactionStatus;
// For testing callback logic
TransactionCallback<String> callback = status -> {
status.setRollbackOnly();
return "result";
};
// Test with simple status
SimpleTransactionStatus testStatus = new SimpleTransactionStatus(true);
String result = callback.doInTransaction(testStatus);
assert testStatus.isRollbackOnly();TransactionTemplate when you need explicit control over transaction boundariesTransactionTemplate is thread-safe and can be reusedTransactionTemplate before executing callbacksstatus.setRollbackOnly() to explicitly mark transaction for rollback@Transactional is simpler and preferred