網頁

2020/10/21

Spring 呼叫同類別的@Transactional方法不會回滾 call same class @Transactional method no rollback

Spring的交易管理(中國叫做事務管理)(Transactoinal Management)中,當由同類別中一個無交易管理(無@Transactional)的方法呼叫另一個有交易管理(有@Transactional)的方法時,有交易管理的方法發生例外時資料不會回滾(rollback)。

例如下面的DemoService類別的modifyName()為無交易管理方法,addModifyLog()為有交易管理方法。

modifyName()中呼叫了addModifyLog(),當addModifyLog()執行發生例外錯誤時不會回滾。

DemoService

package com.abc.demo.service;

import com.abc.demo.entity.ModifyLog;
import com.abc.demo.repository.EmployeeRepository;
import com.abc.demo.repository.ModifyLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Service
public class DemoService {

    @Autowired
    private EmployeeRepository employeeRepository;
    @Autowired
    private ModifyLogRepository modifyLogRepository;

    /**
     * 無交易管理的方法
     */
    public void modifyName(long id, String name) {

        employeeRepository.findById(id).map(e -> {
            e.setName(name);
            return employeeRepository.save(e);
        }).ifPresent(e -> addModifyLog(  // 呼叫同類別中有交易管理的方法
                        e.getClass().getSimpleName().toUpperCase(),
                        e.getId()));

    }

    /**
     * 有交易管理的方法
     */
    @Transactional
    public void addModifyLog(String tableName, long tableId) {
        ModifyLog log = ModifyLog.builder()
                .tableName(tableName)
                .tableId(tableId)
                .modifyDate(new Date())
                .build();

        modifyLogRepository.save(log);

        throw new RuntimeException("Database error.");
    }

}


因為Spring的@Transactional是基於Spring AOP的proxy代理來實現,由客戶端調用的方法為proxy的方法,而被調用的同物件方法則是原物件的方法而非proxy的方法,所以被調用的同物件方法的@Transactional會沒有效果。這也是private方法加上@Transactional也無交易管理的效果的原因。

參考官方文件說明

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).


參考github


沒有留言:

張貼留言