What is a Transaction?
Key Concepts in Transaction Management
- Atomicity: Ensures that all operations within a transaction are completed successfully; if any operation fails, the entire transaction is rolled back.
- Consistency: Ensures that a transaction brings the database from one valid state to another, maintaining database integrity.
- Isolation: Ensures that transactions are executed in isolation from each other, preventing concurrent transactions from interfering with each other.
- Durability: Ensures that once a transaction has been committed, the changes are permanent and will survive system failures like crashes or power outages.
- Begin: The transaction starts.
- Execute: Operations (such as queries and updates) are performed.
- Commit: If all operations are successful, the transaction is committed, making all changes permanent.
- Rollback: If any operation fails, the transaction is rolled back, undoing all changes made during the transaction.
- Locking: Mechanisms to prevent concurrent transactions from interfering with each other by controlling access to data (e.g., read locks, write locks).
- Optimistic Concurrency Control: Transactions execute without locking resources but check for conflicts before committing.
- Pessimistic Concurrency Control: Transactions lock resources before accessing them to prevent conflicts.
- Read Uncommitted: Transactions can read data that has not yet been committed by other transactions.
- Read Committed: Transactions can only read data that has been committed by other transactions.
- Repeatable Read: Ensures that if a transaction reads a value, it will read the same value throughout its execution.
- Serializable: The highest isolation level, ensuring complete isolation from other transactions.
A transaction reads data written by a concurrent uncommitted transaction.
nonrepeatable read:
A transaction re-reads data it has previously read and finds that data has been modified by another transaction (that committed since the initial read). A non-repeatable read occurs when transaction A retrieves a row, transaction B subsequently updates the row, and transaction A later retrieves the same row again. Transaction A retrieves the same row twice but sees different data.
phantom read:
A transaction re-executes a query returning a set of rows that satisfy a search condition and finds that the set of rows satisfying the condition has changed due to another recently-committed transaction. A phantom read occurs when transaction A retrieves a set of rows satisfying a given condition, transaction B subsequently inserts or updates a row such that the row now meets the condition in transaction A, and transaction A later repeats the conditional retrieval. Transaction A now sees an additional row. This row is referred to as a phantom.
serialization anomaly
The result of successfully committing a group of transactions is inconsistent with all possible orderings of running those transactions one at a time.
Spring Transaction Management
Spring transaction management simply means: How does Spring start, commit, or rollback JDBC transactions.Any bean or public method annotated with the @Transactional annotation makes sure that the methods will be executed inside a database transaction.
The @Transactional annotation should be used in the service layer because it is this layer's responsibility to define the transaction boundaries.
How does the @Transactional annotation work ?
Spring creates dynamic proxies for classes that declare @Transactional on the class itself or on methods.
It does that through a method called proxy-through-subclassing with the help of the Cglib library.
It is also worth noting that the proxy itself does not handle these transactional states (open, commit, close); the proxy delegated this work to a transaction manager.
The proxy has access to a transaction manager and can ask it to open and close transactions and connections.
Spring offers a PlatformTransactionManager (extends TransactionManager) interface, which, by default, comes with a couple of handy implementations. One of them is the DataSourceTransactionManager.
Pitfalls
As Spring wraps the bean in the proxy, only calls from "outside" the bean are intercepted. That means, any self-invocation calls will not start any transaction, even if the method has the @Transactional annotation.
Moreover, only public methods should be annotated with @Transactional. Methods of any other visibility will silently ignore the annotation, as these are not proxying.
Transaction Rollback
By default, only RuntimeException and Error trigger a rollback. A checked exception does not trigger a rollback of the transaction.
The @Transactional annotation, on the other hand, supports rollbackFor or rollbackForClassName attributes for rolling back transactions, as well as noRollbackFor or noRollbackForClassName attributes for avoiding rollback.
The @Transactional annotation attributes
The @Transactional annotation provides the following attributes:
1) Propagation
The "propagation" attribute defines how the transaction boundaries propagate to other methods that will be called either directly or indirectly from within the annotated block.
@Service public class OrderServiceI { @Transactional(propagation = Propagation.MANDATORY) public Long addOrder(OrderDto order) { // check inventory // make payment // create order return orderId; } }
There are a variety of propagation modes that can be plugged into the @Transactional method.
Propagation | Meaning |
---|---|
REQUIRED | This is the default propagation. In this case, if no active transaction is found, spring creates one. Otherwise, the method appends to the currently active transaction: |
SUPPORTS | If a transaction exists, then the method uses this existing transaction. If there isn't a transaction, it is executed non-transactional. |
MANDATORY | If there is an active transaction, then it will be used. If there isn't an active transaction, then Spring throws an exception. |
REQUIRES_NEW | Spring suspends the current transaction if it exists and then creates a new one. |
NOT_SUPPORTED | If a current transaction exists, first Spring suspends it, and then the method runs without a transaction. |
NEVER | Spring throws an exception if there's an active transaction. |
NESTED | Spring checks if a transaction exists, and if so, it marks a save point. If method execution throws an exception, then the transaction rolls back to this save point. |
2) ReadOnly
The "readOnly" attribute defines if the current transaction is read-only or read-write.
Common Scenarios Where @Transactional Might Fail
@Transactional
. Since the proxy intercepts calls only from external clients, internal calls bypass the proxy and thus the transaction management.@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public void placeOrder(Order order) {
saveOrder(order); // Direct internal call, bypassing proxy
}
@Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
}
}
saveOrder
is never run within a transactional context if called directly from placeOrder
.Solution: Use external method calls or refactor to use a different service class.
Spring AOP proxies only public methods by default. If you annotate a non-public method with @Transactional
, it won't be transactional because the proxy won't intercept it.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
void updateUser(User user) { // Non-public method
userRepository.save(user);
}
}
Solution: Ensure that all transactional methods are public.
Proxy Types and Configuration
By default, Spring AOP creates proxies based on interfaces (JDK dynamic proxies). If your class does not implement an interface, Spring will use CGLIB proxies. Misconfigurations or misunderstandings about proxy types can lead to unexpected behavior.
Solution: Configure Spring to use class-based (CGLIB) proxies if needed by setting proxyTargetClass=true
.
Misconfigured Proxy Settings
Improper proxy settings in your Spring configuration can also lead to issues. Make sure your Spring application context is set up to scan and process @Transactional
annotations properly.
Solution: Verify your configuration, ensuring @EnableTransactionManagement
is correctly set up.
Example
To avoid these pitfalls, consider the following example:
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Transactional
public void processPayment(Payment payment) {
validatePayment(payment);
savePayment(payment);
}
@Transactional
public void savePayment(Payment payment) {
paymentRepository.save(payment);
}
private void validatePayment(Payment payment) {
// Validation logic
}
}
Public and Transactional Methods:
- Both
processPayment
andsavePayment
methods are public and annotated with@Transactional
. This ensures that when these methods are invoked from outside thePaymentService
class, they are intercepted by the Spring AOP proxy, and transactional behavior is applied.
- When
processPayment
is called from an external class (or any other external source like a controller or another service), the Spring AOP proxy handles the method call. SinceprocessPayment
is marked as@Transactional
, a transaction begins when this method is invoked.
- Inside
processPayment
, thesavePayment
method is called. BecausesavePayment
is also public and annotated with@Transactional
, and because the call originates from a method that was invoked via the proxy, the proxy will correctly handle the transactional behavior ofsavePayment
. As a result, thesavePayment
method will either participate in the existing transaction (if it is part of the same transaction asprocessPayment
) or create a new transaction, depending on the@Transactional
settings.
- Since both
processPayment
andsavePayment
are public and transactional, there’s no self-invocation happening in this scenario. The call tosavePayment
from withinprocessPayment
does not bypass the proxy, because the wholeprocessPayment
method call itself was handled by the proxy when it was initially invoked from outside the class. This ensures that transactional behavior is properly applied to both methods.
- The
validatePayment
method is private and is not marked with@Transactional
. This is intentional because validation typically doesn’t require transactional behavior. By keeping this method private and unannotated, you ensure that it’s only used within the context ofprocessPayment
, which is already transactional. This also keeps the logic clean, as it doesn’t need to be exposed or managed by the transactional proxy.
@Transactional
and Spring AOP simplify transaction management, understanding their limitations is crucial for effective use. By being aware of scenarios like self-invocation, non-public methods, proxy types, and configuration issues, you can avoid common pitfalls and ensure your transactions are managed as expected.
Post a Comment