Why must methods annotated with Transactional be overrideable
Data Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.
The usage of the @Transactional
annotation in Java-based applications, specifically those using the Spring framework, is crucial for managing transaction boundaries in a declarative manner. One aspect that often puzzles developers is why methods annotated with @Transactional
should be overrideable. In this article, we will delve into the technical intricacies of this requirement, explaining it with examples and summarizing key aspects in a digestible format.
Understanding the Role of @Transactional
The @Transactional
annotation is a facet of Spring's declarative transaction management, providing a way to define the scope of a transaction. When applied to a method, it influences the transaction's behavior, such as its isolation level, propagation type, and rollback rules.
How @Transactional
Works in Spring
In Spring, the @Transactional
annotation is typically processed by a proxy, either JDK dynamic proxies or CGLIB bytecode generation. The proxy wraps the annotated method, applying transaction semantics around its execution.
- Proxy Creation: When a bean is managed by Spring and a method within it is annotated with
@Transactional, Spring creates a proxy for this bean. - Transaction Handling: The proxy intercepts method calls, applying the transaction management code before and after the actual method execution.
- Commit/Rollback: Based on method success or failure, the proxy decides whether to commit or roll back the transaction.
The Override Requirement
For @Transactional
methods to work correctly when proxied, they should be overrideable. Here’s why:
- Proxy and Subclassing: CGLIB proxies subclass the original class to add transactional behavior. If a method is
final, it cannot be subclassed, preventing CGLIB from overriding it to implement the transactional logic. - Design Flexibility: Allowing methods to be overridden ensures that subclass implementations can choose to extend or customize transactional boundaries.
- Polymorphism: Subclasses overriding parent class methods with transactional annotations can enable or disable transactions as per specific use cases, offering a polymorphic behavior in transaction management.
A Technical Example
Consider a class UserService
with a saveUser
method:
- Interface vs. Implementation: For proxy creation via interfaces (
JDK dynamic proxies), the interface itself must declare methods as non-final. - Performance: While proxies are powerful, they introduce overhead. Understanding proxy-based transaction management helps mitigate performance issues.
- Testing: When writing unit tests, control over transactional behavior through overriding can facilitate the creation of specific test cases.
Related reading
- Why must wait() always be in synchronized block
- Why must wait always be in synchronized block
- Why my Springboot with embbeded tomcat too slow when process first request?
- Why not use java.util.logging?
- Why should I use Hamcrest matcher and assertThat instead of traditional assertXXX methods?
- Why should I use the keyword final on a method parameter in Java?
- Why should Java 8's Optional not be used in arguments
- Why should Java ThreadLocal variables be static

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseData Structures & Algorithms practice on Codemia
Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.