48--Spring @Transactional声明式事物(五)嵌套事物简介

1.引

前面四节已经简单介绍了Spring的事物管理,当然都是基于单个Service单个方法调用下的、最简答的事物管理,还遗留了一些问题,例如事物嵌套处理、RollbackOnly属性说明等等,接下来的篇幅我们着重介绍Spring中的嵌套事物。在介绍之前先来回顾一下Spring中的事物传播特性,并通过一个简单的例子来看感受一下嵌套事物。

传播特性名称 说明
PROPAGATION_REQUIRED 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中
PROPAGATION_SUPPORTS 支持当前事物,如果当前没有事物,则以非事物方式执行
PROPAGATION_MANDATORY 使用当前事物,如果当前没有事物,则抛出异常
PROPAGATION_REQUIRES_NEW 新建事物,如果当前已经存在事物,则挂起当前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式执行,如果当前存在事物,则挂起当前事物
PROPAGATION_NEVER 以非事物方式执行,如果当前存在事物,则抛出异常
PROPAGATION_NESTED 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同

所谓嵌套事物,就是ServiceA调用了ServiceB的方法,不同的service之间互相进行方法调用,就可能会引发嵌套事物的问题。

2.事物嵌套的例子
  • Service
package com.lyc.cn.v2.day09;
public interface AccountService {
    void save() throws RuntimeException;

    void delete() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class AccountServiceImpl implements AccountService {

    private JdbcTemplate jdbcTemplate;

    private static String insert_sql = "insert into account(balance) values (100)";


    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void save() throws RuntimeException {
        System.out.println("==调用AccountService的save方法\n");
        jdbcTemplate.update(insert_sql);

        throw new RuntimeException("==AccountService的save方法手动抛出异常");
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void delete() throws RuntimeException {

        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void beforeCommit(boolean readOnly) {
                System.out.println("==回调,事物提交之前");
                super.beforeCommit(readOnly);
            }

            @Override
            public void afterCommit() {
                System.out.println("==回调,事物提交之后");
                super.afterCommit();
            }

            @Override
            public void beforeCompletion() {
                super.beforeCompletion();
                System.out.println("==回调,事物完成之前");
            }

            @Override
            public void afterCompletion(int status) {
                super.afterCompletion(status);
                System.out.println("==回调,事物完成之后");
            }
        });

        System.out.println("==调用AccountService的dele方法\n");
        jdbcTemplate.update(insert_sql);

        throw new RuntimeException("==AccountService的delete方法手动抛出异常");
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}


package com.lyc.cn.v2.day09;
public interface BankService  {
    void save() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class BankServiceImpl implements BankService {

    private PersonService personService;
    private AccountService accountService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void save() throws RuntimeException {
        System.out.println("==调用BankService的save方法\n");
        personService.save();
        accountService.save();
    }

    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }
}

package com.lyc.cn.v2.day09;
public interface PersonService {
    void save() throws RuntimeException;
}

package com.lyc.cn.v2.day09;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class PersonServiceImpl implements PersonService {

    private JdbcTemplate jdbcTemplate;


    private static String insert_sql = "insert into account(balance) values (100)";

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void save() throws RuntimeException {
        System.out.println("==调用PersonService的save方法\n");
        jdbcTemplate.update(insert_sql);
        //throw new RuntimeException("==PersonService手动抛出异常");
    }


    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

新建三个Service接口及其实现类,AccountService、BankService、PersonService。然后在BankService的save方法中调用了AccountService、PersonService的方法。

  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--开启tx注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--事物管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--数据源-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="liyanchao1989@"/>
    </bean>

    <!--jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--业务bean-->
    <bean id="accountService" class="com.lyc.cn.v2.day09.AccountServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="personService" class="com.lyc.cn.v2.day09.PersonServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="bankService" class="com.lyc.cn.v2.day09.BankServiceImpl">
        <property name="personService" ref="personService"/>
        <property name="accountService" ref="accountService"/>
    </bean>

    <bean id="myTransactionService" class="com.lyc.cn.v2.day09.MyTransactionService">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

</beans>
  • 测试类及结果
@Test
public void test2() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("v2/day09.xml");
    BankService service = ctx.getBean("bankService", BankService.class);
    service.save();
}
==创建了名为:[com.lyc.cn.v2.day09.BankServiceImpl.save]的事物
==调用BankService的save方法

==创建了名为:[com.lyc.cn.v2.day09.PersonServiceImpl.save]的事物
==调用PersonService的save方法

==创建了名为:[com.lyc.cn.v2.day09.AccountServiceImpl.save]的事物
==调用AccountService的save方法

==准备回滚com.lyc.cn.v2.day09.AccountServiceImpl.save
==当前事物并非独立事物,且RollbackOnly为true

==准备回滚com.lyc.cn.v2.day09.BankServiceImpl.save

java.lang.RuntimeException: ==AccountService的save方法手动抛出异常
3.嵌套事物的处理简介

回顾一下前面讲的创建事物的过程,其中有:

// 2.如果当前已经存在事物
// 重点:
// 如果当前已经存在启动的事物,则根据本次要新建的事物传播特性进行评估,以决定对新事物的后续处理
if (isExistingTransaction(transaction)) {
    // Existing transaction found -> check propagation behavior to find out how to behave.
    return handleExistingTransaction(definition, transaction, debugEnabled);
}

这里我们没有分析,因为从这开始就要涉及到事物的嵌套处理了(在AbstractPlatformTransactionManager类的getTransaction方法中)。

因为我们在在BankService的save方法中调用了AccountService、PersonService的方法。那么当BankService调用到另外两个Servcie方法的时候,因为当前已经存在事物(BankService),那么接下来就要根据新事物的传播特性,以决定下一步的处理了。先来感受一下处理过程:

private TransactionStatus handleExistingTransaction(
            TransactionDefinition definition,
            Object transaction,
            boolean debugEnabled) throws TransactionException {

    // 1.PROPAGATION_NEVER --> 以非事物方式执行,如果当前存在事物,则抛出异常。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
        throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
    }

    // 2.以非事物方式执行,如果当前存在事物,则挂起当前事物。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
        if (debugEnabled) { logger.debug("Suspending current transaction");}
        // 重点:挂起已有事物
        Object suspendedResources = suspend(transaction);
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 创建新事物,注意:transaction参数为null,所以这里创建的不是一个真正的事物
        return prepareTransactionStatus(
                definition,
                null,
                false,
                newSynchronization,
                debugEnabled,
                suspendedResources);
    }

    //3.新建事物,如果当前已经存在事物,则挂起当前事物。
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
        if (debugEnabled) {
            logger.debug("Suspending current transaction, creating new transaction with name [" + definition.getName() + "]");
        }
        // 挂起已有事物
        SuspendedResourcesHolder suspendedResources = suspend(transaction);
        try {
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            // 创建事物
            DefaultTransactionStatus status = newTransactionStatus(
                    definition,
                    transaction,
                    true,
                    newSynchronization,
                    debugEnabled,
                    suspendedResources);
            // 开启事物
            doBegin(transaction, definition);
            // 初始化事物同步属性
            prepareSynchronization(status, definition);
            return status;
        }
        catch (RuntimeException | Error beginEx) {
            resumeAfterBeginException(transaction, suspendedResources, beginEx);
            throw beginEx;
        }
    }

    // 4.如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // 如果不允许嵌套事物,则抛出异常
        if (!isNestedTransactionAllowed()) {
            throw new NestedTransactionNotSupportedException("Transaction manager does not allow nested transactions by default - " +
                    "specify 'nestedTransactionAllowed' property with value 'true'");
        }
        if (debugEnabled) {
            logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
        }

        /**
         * 下面对JtaTransactionManager和AbstractPlatformTransactionManager分别进行处理
         */

        // useSavepointForNestedTransaction(),是否为嵌套事务使用保存点
        // 1.对于JtaTransactionManager-->返回false
        // 2.对于AbstractPlatformTransactionManager-->返回true
        if (useSavepointForNestedTransaction()) {
            // Create savepoint within existing Spring-managed transaction,
            // through the SavepointManager API implemented by TransactionStatus.
            // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
            // 创建保存点在现有spring管理事务,通过TransactionStatus SavepointManager API实现。
            // 通常使用JDBC 3.0保存点。永远不要激活Spring同步。
            DefaultTransactionStatus status = prepareTransactionStatus(definition,transaction,
                    false,
                    false,
                    debugEnabled,
                    null);
            // 创建保存点
            status.createAndHoldSavepoint();
            return status;
        }
        else {
            // Nested transaction through nested begin and commit/rollback calls.
            // Usually only for JTA: Spring synchronization might get activated here
            // in case of a pre-existing JTA transaction.
            // 通过嵌套的开始,提交调用,及回滚调用进行嵌套事务。
            // 只对JTA有效,如果已经存在JTA事务,这里可能会激活Spring同步。
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            DefaultTransactionStatus status = newTransactionStatus(
                    definition,
                    transaction,
                    true,
                    newSynchronization,
                    debugEnabled,
                    null);
            doBegin(transaction, definition);
            prepareSynchronization(status, definition);
            return status;
        }
    }

    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    // 处理PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED两种传播特性
    // PROPAGATION_REQUIRED --> 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
    // PROPAGATION_SUPPORTS --> 支持当前事物,如果当前没有事物,则以非事物方式执行。
    if (debugEnabled) {
        logger.debug("Participating in existing transaction");
    }
    // 对于PROPAGATION_SUPPORTS和PROPAGATION_REQUIRED
    // 新事物参与已有事物时,是否验证已有事物.此属性值默认为false;
    // 如开启将验证新事物和已有事物的隔离级别和事物只读属性是否相同
    if (isValidateExistingTransaction()) {
        // 验证事物隔离级别
        // 如果当前事物的隔离级别不为默认隔离级别,则比较当前事物隔离级别与已有事物隔离级别,
        // 如不同,则抛出事物隔离级别不兼容异常
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
            if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
                Constants isoConstants = DefaultTransactionDefinition.constants;
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] specifies isolation level which is incompatible with existing transaction: " +
                        (currentIsolationLevel != null ?
                                isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                                "(unknown)"));
            }
        }
        // 验证事物只读属性
        // 如果当前事物可写,但是已有的事物是只读,则抛出异常
        if (!definition.isReadOnly()) {
            if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
                throw new IllegalTransactionStateException("Participating transaction with definition [" +
                        definition + "] is not marked as read-only but existing transaction is");
            }
        }
    }
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

在该方法中,对于Spring提供的传播特性分别做了不同的处理,那么接下来我们会对这些传播特性一一分析,包括事物的后续处理、事物的回滚等。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容