80 % der Menschen können den Wissenspunkt nicht erkennen - das Verhalten der Transaktionsweitergabe im Frühling

Kostenlose Buchlieferung: juejin.cn/post/712095…

Ich habe viele Leute interviewt, und die meisten von ihnen können über die vier Merkmale und Isolationsstufen von Transaktionen sprechen, aber wenn sie nach dem Ausbreitungsverhalten von Spring gefragt werden, kann im Grunde niemand eins, zwei, drei sagen.

Wir alle wissen, dass eine Transaktion entweder erfolgreich ist oder fehlschlägt. Aber wenn mehrere Transaktionen zusammenarbeiten, um eine komplexe Aufgabe zu erledigen, ist es nicht einfach, auf diese Weise durch die Bank zu gehen. Wir müssen spezifizieren, welche Tasks gemäß der Beziehung zwischen Tasks zusammen zurückgesetzt werden müssen und welche Tasks andere Tasks nicht beeinflussen, selbst wenn sie fehlschlagen. Um dieses Problem zu lösen, müssen Sie das Ausbreitungsverhalten von Transaktionen verstehen. Es gibt sieben Transaktionsweitergabeverhalten in Spring, wie in der folgenden Tabelle dargestellt:

Verhaltenstyp der Transaktionsweitergabe veranschaulichen
PROPAGATION_REQUIRED Wenn es keine aktuelle Transaktion gibt, erstellen Sie eine neue Transaktion, wenn es bereits eine Transaktion gibt, treten Sie der Transaktion bei. Dies ist die häufigste Wahl.
PROPAGATION_UNTERSTÜTZT Unterstützung der aktuellen Transaktion, wenn keine aktuelle Transaktion vorhanden ist, wird sie in einem nicht transaktionalen Modus ausgeführt.
PROPAGATION_MANDATORY Verwenden Sie die aktuelle Transaktion oder lösen Sie eine Ausnahme aus, wenn keine aktuelle Transaktion vorhanden ist.
PROPAGATION_REQUIRES_NEW Erstellen Sie eine neue Transaktion. Wenn es eine aktuelle Transaktion gibt, setzen Sie die aktuelle Transaktion aus.
PROPAGATION_NOT_SUPPORTED Führen Sie die Operation auf nicht transaktionale Weise aus und unterbrechen Sie die aktuelle Transaktion, wenn eine aktuelle Transaktion vorhanden ist.
PROPAGATION_NIEMALS Führt nicht transaktional aus und löst eine Ausnahme aus, wenn derzeit eine Transaktion vorhanden ist.
PROPAGATION_NESTED Wenn derzeit eine Transaktion vorhanden ist, führen Sie sie innerhalb einer verschachtelten Transaktion aus. Wenn es keine aktuellen Transaktionen gibt, tun Sie etwas Ähnliches wie PROPAGATION_REQUIRED.

Spring kann über das Propagation-Attribut der Annotation @Transactional verschiedene Strategien für das Ausbreitungsverhalten festlegen. Spring stellt dafür eine Aufzählungsklasse Propagation zur Verfügung, der Quellcode sieht wie folgt aus:

package org.springframework.transaction.annotation;
​
import org.springframework.transaction.TransactionDefinition;
​
public enum Propagation {
​
    /**
     * 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,
     * 否则新建一个事务运行内部方法
     */
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
​
    /**
     * 支持事务,如果当前存在事务,就沿用当前事务,
     * 如果不存在,则继续采用无事务的方式运行内部方法
     */
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
​
    /**
     * 必须使用事务,如果当前没有事务,则会抛出异常,
     * 如果存在当前事务,则沿用当前事务
     */
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
​
    /**
     * 无论当前事务是否存在,都会创建新事务运行方法,
     * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
     */
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
​
    /**
     * 不支持事务,当前存在事务时,将挂起事务,运行方法
     */
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
​
    /**
     * 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
     */
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
​
    /** 
     * 在当前方法调用内部方法时,如果内部方法发生异常,
     * 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务
     */
    NESTED(TransactionDefinition.PROPAGATION_NESTED);
​
        ......
​
}

接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。

REQUIRED

当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
​
    @Autowired
    private UserMapper mapper;
    
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addWithRequired(User user) {
        mapper.insert(user);
    }
​
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addWithRequiredAndException(User user) {
        mapper.insert(user);
        throw new RuntimeException();
    }
}

创建 TransactionServiceImpl 类,并添加如下方法:

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
​
    @Autowired
    private UserService userService;
​
    @Override
    public void noTransaction_required_required_externalException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequired(xiaoJing);
        throw new RuntimeException();
        
    }
​
    @Override
    public void noTransaction_required_requiredException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredAndException(xiaoJing);
        
    }
​
    @Override
    @Transactional
    public void transaction_required_required_externalException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequired(xiaoJing);
        throw new RuntimeException();
        
    }
​
    @Override
    @Transactional
    public void transaction_required_requiredException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredAndException(xiaoJing);
        
    }
​
​
    @Override
    @Transactional
    public void transaction_required_requiredException_try() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        try {
            userService.addWithRequiredAndException(xiaoJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!");
        }
        
    }
}

结果分析如下表所示:

方法 结果 分析
noTransaction_required_required_externalException 小水和小镜均成功入库 外部方法未开启事务,所以所有插入操作均未受到外部异常影响
noTransaction_required_requiredException 小水入库,小镜未入库 外部方法未开启事务,内部方法事务各自独立,互不影响,「小镜」的插入方法发生异常回滚,但「小水」的插入方法不受影响
transaction_required_required_externalException 小水和小镜均未入库 外部方法开启事务,所有内部方法均加入外部方法的事务中。而外部方法发生异常,所以导致所有操作都发生回滚
transaction_required_requiredException 小水和小镜均未入库 外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,所以导致所有操作都发生回滚
transaction_required_requiredException_try 小水和小镜均未入库 外部方法开启事务,所有内部方法均加入外部方法的事务中。由于「小镜」的插入方法发生异常,此时所有方法都处于同一个事务中,即使发生异常的部分被 try-catch 住,所有操作仍然会回滚

前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。

PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。

REQUIRES_NEW

当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
​
    ...
​
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addWithRequiredNew(User user) {
        
        mapper.insert(user);
        
    }
​
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addWithRequiredNewAndException(User user) {
        
        mapper.insert(user);
        throw new RuntimeException();
        
    }
}

在 TransactionServiceImpl 中添加如下方法:

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
​
    ...
​
    @Override
    @Transactional
    public void transaction_required_requiredNew_externalException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        throw new RuntimeException();
        
    }
​
    @Override
    @Transactional
    public void transaction_required_requiredNew_requiredNewException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        userService.addWithRequiredNewAndException(shuiJing);
        
    }
​
    @Override
    @Transactional
    public void transaction_required_requiredNewException_try() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        try {
            userService.addWithRequiredNewAndException(shuiJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!");
        }
        
    }
}

结果分析如下表所示:

方法 结果 分析
transaction_required_requiredNew_externalException 小水未入库,小镜入库 外部方法开启事务,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法开启一个独立的新事务,不受外部方法异常的影响
transaction_required_requiredNew_requiredNewException 小水未入库,小镜入库,水镜未入库 外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法和外部方法在同一个事务中,跟随外部方法发生回滚;「小镜」的插入方法也会开启一个独立的新事务,因此不会受到任何方法的影响,成功入库
transaction_required_requiredNewException_try 小水和小镜入库,水镜未入库 外部方法开启事务,「水镜」的插入方法开启一个独立的新事务,因为发生异常,所以自己回滚了;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

NESTED

当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
​
    ...
​
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addWithNested(User user) {
        
        mapper.insert(user);
        
    }
​
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addWithNestedAndException(User user) {
        
        mapper.insert(user);
        throw new RuntimeException();
        
    }
}

在 TransactionServiceImpl 中添加如下方法:

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
​
    ...
​
    @Override
    @Transactional
    public void transaction_nested_nested_externalException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithNested(xiaoShui);
        userService.addWithNested(xiaoJing);
        throw new RuntimeException();
        
    }
​
    @Override
    @Transactional
    public void transaction_nested_nestedException() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithNested(xiaoShui);
        userService.addWithNestedAndException(xiaoJing);
        
    }
​
    @Override
    @Transactional
    public void transaction_nested_nestedException_try() {
        
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithNested(xiaoJing);
        try {
            userService.addWithNestedAndException(shuiJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!",e);
        }
        
    }
}

结果分析如下表所示:

方法 结果 分析
transaction_nested_nested_externalException 小水和小镜均未入库 外部方法开启事务,内部方法开启各自的子事务,外部方法发生异常,主事务回滚,子事务跟随主事务回滚
transaction_nested_nestedException 小水和小镜均未入库 外部方法开启事务,内部方法开启各自的子事务,「小镜」的插入方法发生异常回滚自己的子事务;「小镜」的异常没有做处理,因此会被外部方法感知到,「小水」的插入方法在外部方法的子事务中,所以跟随主事务回滚
transaction_nested_nestedException_try 小水和小镜入库,水镜未入库 外部方法开启事务,「小镜」和「水镜」开启各自的子事务,「小水」加入外部方法的事务。「水镜」的插入方法发生异常回滚自己的子事务;「水镜」的异常被 try-catch 处理了,其他方法正常提交「小水」和「小镜」成功入库

每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。

总结

REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。

Wenn es keine externe Transaktion gibt, öffnet REQUIRED eine unabhängige neue Transaktion und wirkt sich nicht auf andere Transaktionen auf derselben Ebene aus; und wenn es eine externe Transaktion gibt, lebt und stirbt es mit der externen Transaktion.

NESTED hat die gleiche Wirkung wie REQUIRED, wenn keine äußere Transaktion vorhanden ist; während bei Vorhandensein einer äußeren Transaktion, wenn die äußere Transaktion zurückgesetzt wird, eine verschachtelte Transaktion (Untertransaktion) erstellt wird. Wenn die externe Transaktion zurückgesetzt wird, wird die Untertransaktion zurückgesetzt, aber das Zurücksetzen der Untertransaktion wirkt sich nicht auf die externe Transaktion und andere Transaktionen auf derselben Ebene aus.

Weitere exklusive und spannende Inhalte in meinem neuen Buch „ Spring Boot Fun Practical Lessons “.

Ich denke du magst

Origin juejin.im/post/7120900365658095624
Empfohlen
Rangfolge