Spring事務與非事務方法相互調用

事務

1. 事務的4種特性        

序號 參數 含義
1 原子性(Atomicity) 事務是數據庫的邏輯工作單位,它對數據庫的修改要麼全部執行,要麼全部不執行。
2 一致性(Consistemcy) 事務前後,數據庫的狀態都滿足所有的完整性約束。
3 隔離性(Isolation) 併發執行的事務是隔離的,一個不影響一個。通過設置數據庫的隔離級別,可以達到不同的隔離效果
4 持久性(Durability) 在事務完成以後,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾。


2.Transactional()控制事務傳播的配置項目(默認Propagation.REQUIRED)

@Transactional(propagation=Propagation.REQUIRED) //控制事務傳播。默認是Propagation.REQUIRED
@Transactional(isolation=Isolation.DEFAULT) //控制事務隔離級別。默認跟數據庫的隔離級別相同
@Transactional(readOnly=false) //控制事務可讀寫、只可讀。默認可讀寫
@Transactional(timeout=30) //控制事務的超時時間,單位秒。默認跟數據庫的事務控制系統相同
@Transactional(rollbackFor=RuntimeException.class) //控制事務遇到哪些異常會回滾。默認是RuntimeException
@Transactional(rollbackForClassName=RuntimeException) //同上
@Transactional(noRollbackFor=NullPointerException.class) //控制事務遇到哪些異常不會回滾。默認遇到RuntimeException回滾
@Transactional(noRollbackForClassName=NullPointerException)//同上

3.事務的7中傳播特性

序號 傳播行爲 含義
1 REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啓一個新的事務。
2 SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行
3 MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
4 NESTED 如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行
5 NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常
6 REQUIRES_NEW 總是開啓一個新的事務。如果一個事務已經存在,則將這個已經存在的事務掛起
7 NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務

Spring事務傳播屬性:
1.propagation-required: 支持當前事務,如果有就加入當前事務中;如果當前方法沒有事務,就新建一個事務;
2.propagation-supports: 支持當前事務,如果有就加入當前事務中;如果當前方法沒有事務,就以非事務的方式執行;
3.propagation-mandatory: 支持當前事務,如果有就加入當前事務中;如果當前沒有事務,就拋出異常;
4.propagation-requires_new: 新建事務,如果當前存在事務,就把當前事務掛起;如果當前方法沒有事務,就新建事務;
5.propagation-not-supported: 以非事務方式執行,如果當前方法存在事務就掛起當前事務;如果當前方法不存在事務,就以非事務方式執行;
6.propagation-never: 以非事務方式執行,如果當前方法存在事務就拋出異常;如果當前方法不存在事務,就以非事務方式執行;
7.propagation-nested: 如果當前方法有事務,則在嵌套事務內執行;如果當前方法沒有事務,則與required操作類似;
前六個策略類似於EJB CMT,第七個(PROPAGATION_NESTED)是Spring所提供的一個特殊變量。
它要求事務管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務行爲(如Spring的DataSourceTransactionManager)
在同一個類中,一個方法調用另外一個有註解(比如@Async,@Transational)的方法,註解是不會生效的

 

4. 事務的傳播案例:

事務在A類的a()方法中調用B類的b()方法的傳播案例

A.a() B.b()的事務配置 a()沒有事務的結果 a()有事務的結果
REQUIRED b()創建自己的事務; b()接受a()的事務
SUPPORTS b()不創建自己的事務; b()接受a()的事務
MANDATORY b()報異常 b()接受a()的事務
NESTED b()創建自己的事務; b()接受a()的事務,成爲a()嵌套的子事務
NEVER b()不創建自己的事務; b()報異常
REQUIRES_NEW b()創建自己的事務; b()不接受a()的事務,b()先執行,內層事務失敗不會影響外層事務
NOT_SUPPORTED b()不創建自己的事務; b()不接受a()的事務,b()先執行

 

事務不生效的場景

問題一:非事務方法A調用事務方法B,方法B事務不生效

spring默認的是PROPAGATION_REQUIRED機制,如果方法A標註了註解@Transactional 是完全沒問題的,執行的時候傳播給方法B,因爲方法A開啓了事務,線程內的connection的屬性autoCommit=false,並且執行到方法B時,事務傳播依然是生效的,得到的還是方法A的connection,autoCommit還是爲false,所以事務生效;反之,如果方法A沒有註解@Transactional 時,是不受事務管理的,autoCommit=true,那麼傳播給方法B的也爲true,執行完自動提交,即使B標註了@Transactional ;

在一個Service內部,事務方法之間的嵌套調用,普通方法和事務方法之間的嵌套調用,都不會開啓新的事務.是因爲spring採用動態代理機制來實現事務控制,而動態代理最終都是要調用原始對象的,而原始對象在去調用方法時,是不會再觸發代理了!

所以以上就是爲什麼在沒有標註事務註解的方法A裏去調用標註有事務註解的方法B而沒有事務滾回的原因;

 

問題二:在事務方法A中調用另外一個事務方法B,被調用方法B的事務沒起作用

 

產生問題的原因是:spring是通過代理代管理事務的,當在第一個方法insertUser1內直接調用insertUser2的時候 ,insertUser2上的事務是不會起作用的(也就是insertUser2是沒有開啓事務)

 

解決方案

開啓一下 有關事務 的日誌,在 配置文件 中加上下面的配置:

logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=debug

 

1.把方法放到不同的類:

我們需要新建一個接口:

public interface OtherService {
    void insertCodeMonkey();
}

再定義一個類去實現這個接口:

@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    AccountMapper mapper;

    @Override
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
        int a = 1 / 0;
    }
}

修改原本的實現類:

@Service
public class AccountSerivceImpl implements AccountService {

    @Autowired
    AccountMapper mapper;

    @Autowired
    OtherService otherService;

    @Transactional
    @Override
    public void insertCodeBear() {

        try {
            otherService.insertCodeMonkey();
        } catch (Exception e) {
            e.printStackTrace();
        }

        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }
}

運行,查看數據庫:

 

只有一條數據,insertCodeBear方法執行成功了,insertCodeMonkey執行失敗,並且回滾了。

讓我們再看看控制檯的日誌:

可以看到是開了兩個事務去執行的。

這種解決方案最簡單,不需要了解其他東西,但是這種方案需要修改代碼結構,本來兩個方法都是屬於同一個類的,現在需要強行把它們拆開。

 

2. AopContext:

我們的目標是要在實現類中獲取本類的代理對象,Spring提供了Aop上下文,即:AopContext,通過AopContext,可以很方便的獲取到代理對象:

@Service
public class AccountSerivceImpl implements AccountService {

    @Autowired
    AccountMapper mapper;

    @Transactional
    @Override
    public void insertCodeBear() {
        try {
            ((AccountService)AopContext.currentProxy()).insertCodeMonkey();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
        int a = 1 / 0;
    }
}

當寫好代碼,很愉快的去測試,發現竟然報錯了:

 

翻譯下:不能找到當前的代理,需要設置exposeProxy屬性爲 true使其可以。

expose字面意思就是 暴露。也就是說 我們需要允許暴露代理。

我們需要在Spring Boot啓動類上+一個註解:

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
@MapperScan(basePackages = "com.codebear.Dao")
public class SpringbootApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

再次運行:

確實是開啓了兩個事務去執行的。

再看看數據庫,也沒有問題。

 

3. ApplicationContext:

@Service
public class AccountSerivceImpl implements AccountService {

    @Autowired
    AccountMapper mapper;

    @Autowired
    ApplicationContext context;

    AccountService service;

    @PostConstruct
    private void setSelf() {
        service = context.getBean(AccountService.class);
    }

    @Transactional
    @Override
    public void insertCodeBear() {
        try {
            service.insertCodeMonkey();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
        int a = 1 / 0;
    }
}

此方法不適用於prototype

在這裏,我用了一個@PostConstruct註解,在初始化的時候,會調用被@PostConstruct標記的方法(注意,僅僅是初始化的時候,纔會被調用。以後都不會被調用了,大家可以打個斷點試一下),這裏這麼做的目的就是爲了提升一下效率,不用每次都getBean。所以如果這個類是prototype的,就不適用這個方法了。如果是prototype的話,就在insertCodeBear方法中使用getBean方法吧。

上兩種方法比較方便,沒有新建其他的接口或者是類,但是沒有很好的封裝獲得Aop代理對象的過程,也不是很符合 迪比特法則,也就是最少知識原則。

 

4. 重寫BeanPostProcessor接口:

關於這個接口是做什麼的,這裏就不詳細闡述了,簡單的來說這是Spring提供的接口,我們可以通過重寫它,在初始化Bean之前或者之後,自定義一些額外的邏輯。
首先,我們需要定義一個接口:

public interface WeavingSelfProxy {
    void setSelfProxy(Object bean);
}

要獲得代理對象的類,需要去實現它:

@Service
public class AccountSerivceImpl implements AccountService, WeavingSelfProxy {

    @Autowired
    AccountMapper mapper;

    AccountService service;

    @Override
    public void setSelfProxy(Object bean) {
        System.out.println("進入到setSelfProxy方法");
        service = (AccountService) bean;
    }

    @Transactional
    @Override
    public void insertCodeBear() {
        try {
            service.insertCodeMonkey();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Account account = new Account();
        account.setAccount("CodeBear");
        account.setPassword("CodeBear");
        mapper.insert(account);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertCodeMonkey() {
        Account account = new Account();
        account.setAccount("CodeMonkey");
        account.setPassword("CodeMonkey");
        mapper.insert(account);
        int a = 1 / 0;
    }
}

重寫BeanPostProcessor接口:

@Component
public class SetSelfProxyProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof WeavingSelfProxy){
            System.out.println("實現了WeavingSelfProxy接口");
            ((WeavingSelfProxy) bean).setSelfProxy(bean);
        }
        return bean;
    }
}

 

 

參考:

@Transactional 同一個類中無事務方法a()內部調用有事務方法b()的問題https://www.cnblogs.com/lukelook/p/11246180.html

被標記爲事務的方法互相調用的坑(上):https://www.jianshu.com/p/ba13144dba20?from=timeline&isappinstalled=0

被標記爲事務的方法互相調用的坑(下):https://www.jianshu.com/p/b375c52f2a71

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章