When Spring @Transactional Fails: Common Pitfalls and Fixes

This article explains why Spring @Transactional may not work as expected, covering issues such as wrong method visibility, final methods, internal calls, missing bean registration, multithreading, unsupported table engines, incorrect propagation settings, swallowed exceptions, and how to resolve each problem.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
When Spring @Transactional Fails: Common Pitfalls and Fixes

Transaction Not Effective

Method Visibility

Spring creates a proxy for a bean and only public methods can be intercepted. If a @Transactional method is declared private, protected or package‑private, Spring returns null for the transaction attribute and the method runs without a transaction.

@Service
public class UserService {
    @Transactional
    private void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

Final Methods

Methods marked final cannot be overridden by the proxy class, so the transactional advice is never applied.

@Service
public class UserService {
    @Transactional
    public final void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}
Static methods suffer the same problem because they cannot be proxied.

Internal Method Calls

Calling a @Transactional method from another method of the same class uses the this reference, bypassing the proxy. Consequently, the inner method runs without a transaction.

@Service
public class UserService {
    @Transactional
    public void add(UserModel userModel) {
        saveData(userModel);
        updateStatus(userModel); // internal call – no transaction
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        // transactional logic
    }
}

Solutions:

Add a separate service method and inject it, then call the new bean.

Inject the service into itself (self‑injection) and call the method on the injected proxy.

Use AopContext.currentProxy() to obtain the proxy and invoke the method.

// Self‑injection example
@Service
public class ServiceA {
    @Autowired
    private ServiceA self;

    public void save(User user) {
        // ...
        self.doSave(user); // goes through proxy
    }

    @Transactional
    public void doSave(User user) {
        // transactional work
    }
}

Bean Not Managed by Spring

If a class lacks a stereotype annotation such as @Service, Spring will not create a bean for it, and any @Transactional method inside will never be proxied.

// Missing @Service – no transaction
public class UserService {
    @Transactional
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

Multithreaded Calls

Transactional context is bound to the current thread. When a transaction‑annotated method spawns a new thread and invokes another @Transactional method inside that thread, the second method runs without the original transaction and cannot roll back the outer work.

@Service
public class UserService {
    @Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        new Thread(() -> roleService.doOtherThing()).start();
    }
}

@Service
public class RoleService {
    @Transactional
    public void doOtherThing() {
        System.out.println("save role data");
    }
}

Table Engine Does Not Support Transactions

MyISAM tables in MySQL do not support transactions. If a table used in a multi‑table operation is MyISAM, the operation cannot be rolled back, leading to data inconsistency.

CREATE TABLE `category` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `one_category` VARCHAR(20),
  `two_category` VARCHAR(20),
  `three_category` VARCHAR(20),
  `four_category` VARCHAR(20),
  PRIMARY KEY (`id`)
) ENGINE=MyISAM;
Always verify that the underlying tables use a transactional engine such as InnoDB before relying on Spring transactions.

Transaction Not Enabled (XML Configuration)

In a plain Spring project you must configure a DataSourceTransactionManager and enable transaction advice. Missing or incorrect configuration (e.g., wrong pointcut expression) will prevent transactions from being applied.

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="pointcut" expression="execution(* com.example..*.*(..))"/>
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>

Transaction Does Not Roll Back

Wrong Propagation Setting

Using a propagation type that does not create a transaction (e.g., Propagation.NEVER) will cause the method to run without a transaction, so no rollback can occur.

@Service
public class UserService {
    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

Swallowing Exceptions

If a @Transactional method catches exceptions and does not re‑throw them, Spring assumes the method completed successfully and will not roll back.

@Transactional
public void add(UserModel userModel) {
    try {
        saveData(userModel);
        updateData(userModel);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        // no re‑throw → no rollback
    }
}

Throwing Non‑Rollback Exceptions

By default Spring rolls back only on RuntimeException and Error. Throwing a checked Exception without configuring rollbackFor will not trigger a rollback.

@Transactional
public void add(UserModel userModel) throws Exception {
    try {
        saveData(userModel);
        updateData(userModel);
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new Exception(e); // still a checked exception → no rollback
    }
}

Misconfigured rollbackFor

If rollbackFor is set to a custom exception that never occurs, the transaction will not roll back for the actual database exceptions (e.g., SQLException).

@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
    saveData(userModel);
    updateData(userModel);
    // SQLException is thrown → not rolled back because it is not BusinessException
}

Nested Transaction Rollback Scope

When using Propagation.NESTED, an exception in the inner transaction propagates to the outer transaction unless it is caught. To roll back only the inner transaction, catch the exception inside the outer method.

@Transactional
public void add(UserModel userModel) throws Exception {
    userMapper.insertUser(userModel);
    try {
        roleService.doOtherThing(); // NESTED transaction
    } catch (Exception e) {
        log.error(e.getMessage(), e);
        // inner transaction rolled back, outer continues
    }
}

Other Transaction Topics

Large Transaction Problem

Annotating a whole method with @Transactional can unintentionally include many read‑only queries, leading to long‑running transactions and performance issues. Only the statements that modify data should be placed inside a transaction.

// Only the two lines below really need a transaction
roleService.save(userModel);
update(userModel);

Programmatic (Declarative vs. Programmatic) Transactions

Spring also provides programmatic transaction management via TransactionTemplate. This approach gives finer control over transaction boundaries and avoids proxy‑related pitfalls.

@Autowired
private TransactionTemplate transactionTemplate;

public void save(final User user) {
    queryData1();
    queryData2();
    transactionTemplate.execute(status -> {
        addData1();
        updateData2();
        return Boolean.TRUE;
    });
}
Use programmatic transactions when you need precise control or when AOP proxy limitations cause @Transactional to fail.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backendTransaction Management
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.