Explain Codes LogoExplain Codes Logo

Spring @Transactional method call by the method within the same class, does not work?

java
transactional
spring
aop
Nikita BarsukovbyNikita Barsukov·Nov 2, 2024
TLDR

To make @Transactional operations work when called from within the same class, you need to access via a proxy, preferably through self-injection:

@Service public class TransactionalService { @Autowired private TransactionalService self; public void runTransaction() { // Like talking to yourself, but in programming self.doTransactionalStuff(); } @Transactional public void doTransactionalStuff() { // Transactional magic happens here! } }

This technique gives you the proxy instance and ensures that transactional support applies to internal method calls.

Why self-injection is necessary

Spring uses AOP proxies for transaction management. When you call a @Transactional method, Spring checks if it's a proxy call. If you call the method directly (this.doTransactionalStuff()), it bypasses the proxy, and hence Spring's transaction management.

To handle this, use self-injection: the class injects an instance of itself. Another approach involves initializing a proxy in a @PostConstruct method to confirm transaction management applies for internal calls:

@Service public class TransactionalService { private TransactionalService self; @Autowired private ApplicationContext context; @PostConstruct public void init() { // Getting a mirror image of myself self = context.getBean(TransactionalService.class); } // ... (other methods) }

Alternatively, refactor your code: move transactional methods to a different class or interface. In other words, create a wingman for your transaction management:

@Service public class TransactionalService { // ... (other non-transactional methods) } @Transactional @Service public class TransactionalOperations { public void perform() { // The hardworking wingman doing its magic } }

Deeper transaction control with AspectJ

For transaction management at a granular level, switch to AspectJ mode. This is particularly useful for private method transactions with older Spring versions that throw a tantrum with the default proxy:

<aop:config proxy-target-class="true"> <aop:aspectj-autoproxy /> </aop:config>

With AspectJ, @Transactional works even for non-public methods. Simply apply @Transactional at the class level or configure AspectJ pointcuts to hit bullseye every time.

Manual transaction control: you're the boss

If automatic transaction management isn't sufficient, manually control your transactions. Harness the power of Spring's Transaction API and be the boss of your transactions. Use either TransactionTemplate or TransactionAspectSupport to your convenience:

public void complexOperation() { new TransactionTemplate(transactionManager).execute(status -> { // You're the boss here status.setRollbackOnly(); // <-- Control freak's delight return null; }); }

If you suspect a transaction could fail, catch exceptions and roll back:

try { // Business logic with a likelihood of throwing tantrums } catch (TemperamentalException ex) { // Handling tantrums like a boss and rolling back TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); }

Ensuring transaction consistency: the right propagation

Different propagation settings control how transactions propagate. Use REQUIRED, REQUIRES_NEW, or NESTED etc., based on your business needs. Choosing the right propagation level can help you achieve transaction consistency:

@Transactional(propagation = Propagation.REQUIRES_NEW) public void transactionWithNewScope() { // New transactional scope code }

If class methods call each other and need to be transactional, apply @Transactional at the class level:

@Transactional @Service public class TransactionalService { // Now all methods smell transactional! }

Dynamic objects and cglib limitations

When your application creates dynamic objects or uses cglib for proxying, remember:

  • Proxies for dynamic objects may behave differently.
  • Cglib proxies can't proxy final methods, leaving them non-transactional.

Configure your Spring to select the right strategy for your proxying needs:

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>