美文网首页
@Transactional

@Transactional

作者: eagle_king | 来源:发表于2019-04-11 10:23 被阅读0次

@Transactional 可以继承,并且使用方法级别优先类级别和最近原则,而且不会将所有的 @Transactional 结合使用。

虽然只读标志被设置为 true,但是事务传播模式被设置为 SUPPORTS,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。只读标志只在事务启动时应用。在本例中,因为没有启动任何事务,所以只读标志被忽略。

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public Transaction saveReadOnlyAndSupports(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        return save;
    }

使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,并且一条基于 ORM 框架的指令(本例中是 Hibernate)是将对象缓存的 flush 模式设置为 NEVER,表示在这个工作单元中,该对象缓存不应与数据库同步。不过,REQUIRED 传播模式会覆盖所有这些内容,允许事务启动并工作,就好像没有设置只读标志一样。

@Transactional 注释的默认传播模式是 REQUIRED。这意味着事务会在不必要的情况下启动。根据使用的数据库,这会引起不必要的共享锁,可能会使数据库中出现死锁的情况。此外,启动和停止事务将消耗不必要的处理时间和资源。总的来说,在使用基于 ORM 的框架时,只读标志基本上毫无用处,在大多数情况下会被忽略。但如果您坚持使用它,请记得将传播模式设置为SUPPORTS,这样就不会启动事务。

但是现在使用save方法时会抛出这个异常: java.lang.ClassCastException:org.hibernate.action.internal.DelayedPostInsertIdentifier cannot be cast to java.math.BigInteger(主键类型);这个可能是 Hibernate的 bug。

只读标记,意味着只能查询,但是查询并不会造成线程失败,所有只读并不具有很大的意义。

注意:不管是使用 Spring Framework,还是使用 EJB,使用 REQUIRES_NEW 事务属性都会得到不好的结果并导致数据损坏和不一致。REQUIRES_NEW 事务属性总是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是确保事务启动的正确方法。

注意:Spring 官方问答中提示,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 markedwith @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so youshould not rely on this feature in your initialization code (that is, @PostConstruct).
在代理模式下(默认情况下),只截获通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)不会在运行时导致实际事务,即使被调用的方法被标记为@transactional。此外,代理必须完全初始化才能提供预期的行为,因此在初始化代码中不应依赖此功能(即@postconstruct)。

大致的意思:
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截。

Double REQUIRED
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于同一事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于同一事务中,两个插入操作都回滚
3,外部方法抛异常 内部方法和外部方法处于同一事务中,两个插入操作都回滚

Double REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交

REQUIRED + REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交

完整例子:
TransactionRepository.java

package com.xiaomi.springbootbasetest.repository;

import com.xiaomi.springbootbasetest.model.entity.Transaction;
import java.math.BigInteger;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

/**
 * @author king
 * @date 2019-02-13
 */
public interface TransactionRepository extends JpaRepository<Transaction, Long> {

    @Query("update Transaction set message = :message where id = :id")
    @Modifying(flushAutomatically = true, clearAutomatically = true)
    int update(BigInteger id, String message);

}

TransactionServiceImpl.java

package com.xiaomi.springbootbasetest.service.impl;

import com.xiaomi.springbootbasetest.model.entity.Transaction;
import com.xiaomi.springbootbasetest.repository.TransactionRepository;
import com.xiaomi.springbootbasetest.service.TransactionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author king
 * @date 2019-04-10
 */
@Slf4j
@Service("transactionService")
// @Transactional(noRollbackFor = Exception.class)
public class TransactionServiceImpl implements TransactionService {

    private final TransactionRepository transactionRepository;

    @Autowired
    public TransactionServiceImpl(TransactionRepository transactionRepository) {
        this.transactionRepository = transactionRepository;
    }

    // @Transactional 可以继承,并且使用方法级别优先类级别和最近原则,而且不会将所有的 @Transactional 结合使用

    @Override
    @Transactional
    public Transaction save(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        // int a = 1 / 0;
        // throw new Exception("I am an Exception");
        return save;
    }

    /*
        虽然只读标志被设置为 true,但是事务传播模式被设置为 SUPPORTS,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。
        只读标志只在事务启动时应用。在本例中,因为没有启动任何事务,所以只读标志被忽略。
     */

    @Override
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public Transaction saveReadOnlyAndSupports(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        return save;
    }

    /*
        使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,并且一条基于 ORM 框架的指令(本例中是 Hibernate)是将对象缓存的 flush 模式
        设置为 NEVER,表示在这个工作单元中,该对象缓存不应与数据库同步。不过,REQUIRED 传播模式会覆盖所有这些内容,允许事务启动并工作,
        就好像没有设置只读标志一样。

        @Transactional 注释的默认传播模式是 REQUIRED。这意味着事务会在不必要的情况下启动。根据使用的数据库,这会引起不必要的共享锁,
        可能会使数据库中出现死锁的情况。此外,启动和停止事务将消耗不必要的处理时间和资源。总的来说,在使用基于 ORM 的框架时,
        只读标志基本上毫无用处,在大多数情况下会被忽略。但如果您坚持使用它,请记得将传播模式设置为 SUPPORTS,这样就不会启动事务。

        但是现在使用save方法时会抛出这个异常: java.lang.ClassCastException:
        org.hibernate.action.internal.DelayedPostInsertIdentifier cannot be cast to java.math.BigInteger(主键类型);
        这个可能是 Hibernate的 bug。

        只读标记,意味着只能查询,但是查询并不会造成线程失败,所有只读并不具有很大的意义。
     */

    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
    public Transaction saveReadOnlyAndRequired(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        return save;
    }

    /*
        Double REQUIRED
            1,内部方法抛异常,外部方法不捕获        内部方法和外部方法处于同一事务中,两个插入操作都回滚
            2,内部方法抛异常,外部方法捕获吃掉      内部方法和外部方法处于同一事务中,两个插入操作都回滚
            3,外部方法抛异常                     内部方法和外部方法处于同一事务中,两个插入操作都回滚
     */

    @Override
    @Transactional
    public Transaction saveAndRequired(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        // int a = 1 / 0;
        return save;
    }

    @Override
    @Transactional
    public Transaction saveAndRequired(Transaction transaction1, Transaction transaction2,
            TransactionService transactionService) throws Exception {
        Transaction save = transactionRepository.save(transaction1);
        System.out.println(save);
        // 内部方法
        save = transactionService.saveAndRequired(transaction2);
        System.out.println(save);
        /*try {
            // 内部方法
            save = transactionService.saveAndRequired(transaction2);
            System.out.println(save);
        } catch (RuntimeException e) {

        }*/
        int a = 1 / 0;
        return save;
    }

    /*
        不管是使用 Spring Framework,还是使用 EJB,使用 REQUIRES_NEW 事务属性都会得到不好的结果并导致数据损坏和不一致。
        REQUIRES_NEW 事务属性总是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是确保事务启动的正确方法。
     */

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int updateAndRequiredNew(Transaction transaction) throws Exception {
        int result = transactionRepository.update(transaction.getId(), transaction.getMessage());
        // result = 1 / 0;
        return result;
    }

    /*
        saveAndRequiredNew() 中包含了 updateAndRequiredNew(): 即 后者是前者的一部分,而前者不是后者的一部分;
        所以如果后者抛异常即意味着前者也一定会抛异常,但是如果前者抛异常并不意味着后者一定会抛异常。[理想状态]
     */

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Transaction saveAndRequiredNew(Transaction transaction) throws Exception {
        Transaction save = transactionRepository.save(transaction);
        // int a = 1 / 0;
        return save;
    }

    /*
        [实际状态] 这两个嵌套的方法,无论哪里抛出异常,两个方法都会回滚,这样的话,REQUIRES_NEW 与 REQUIRES 岂不是一样一样的啦,伤脑筋呀!!
        等等,它好像是指对不同表的操作。
            这是类内的方法相互调用,默认 JDK动态代理 不会截获设置事务。
     */

    /*
        Double REQUIRES_NEW
            1,内部方法抛异常,外部方法不捕获        内部方法和外部方法处于两个事务中,两个插入操作都回滚
            2,内部方法抛异常,外部方法捕获吃掉      内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
            3,外部方法抛异常                     内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
     */

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Transaction saveAndRequiredNew(Transaction transaction1, Transaction transaction2,
            TransactionService transactionService) throws Exception {
        Transaction save = transactionRepository.save(transaction1);
        System.out.println(save);

        /*
        // 内部方法
        save = transactionService.saveAndRequiredNew(transaction2);
        System.out.println(save);
        */
        try {
            // Transaction save2 = saveAndRequiredNew(transaction2);
            save = transactionService.saveAndRequiredNew(transaction2);
            System.out.println(save);
        } catch (RuntimeException e) {

        }

        int a = 1 / 0;
        return save;
    }

    /*
        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).

        在代理模式下(默认情况下),只截获通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)
        不会在运行时导致实际事务,即使被调用的方法被标记为@transactional。此外,代理必须完全初始化才能提供预期的行为,
        因此在初始化代码中不应依赖此功能(即@postconstruct)。

        大致的意思:
            在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,
            是不会被 Spring 的事务拦截器拦截。
     */

    /*
        REQUIRED + REQUIRES_NEW
            1,内部方法抛异常,外部方法不捕获        内部方法和外部方法处于两个事务中,两个插入操作都回滚
            2,内部方法抛异常,外部方法捕获吃掉      内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
            3,外部方法抛异常                     内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
     */

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Transaction saveAndRequiredAndRequiredNew(Transaction transaction1, Transaction transaction2,
            TransactionService transactionService) throws Exception {
        Transaction save = transactionRepository.save(transaction1);
        System.out.println(save);
/*
        // 内部方法
        save = transactionService.saveAndRequiredNew(transaction2);
        System.out.println(save);*/

        try {
            // 注意这一行代码,是通过外部传入的 transactionService 对象调用的。
            save = transactionService.saveAndRequiredNew(transaction2);
            System.out.println(save);
        } catch (RuntimeException e) {

        }
        int a = 1 / 0;
        return save;
    }

}

TransactionServiceTest.java

package com.xiaomi.springbootbasetest.service;

import com.xiaomi.springbootbasetest.model.entity.Transaction;
import java.math.BigInteger;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author king
 * @date 2019-04-10
 */
@RunWith(SpringRunner.class)
@SpringBootTest
/*
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ComponentScan("com.xiaomi")
*/ public class TransactionServiceTest {

    @Autowired
    private TransactionService transactionService;

    @Test
    public void save() throws Exception {
        Transaction transaction = Transaction.builder().type("Exception").message("普通异常测试").build();
        Transaction save = transactionService.save(transaction);
        Assertions.assertThat(save).isNotNull();
        System.out.println(save);
    }

    @Test
    public void saveReadOnlyAndSupports() throws Exception {
        Transaction transaction = Transaction.builder().type("Exception").message("只读和Supports异常测试").build();
        Transaction save = transactionService.saveReadOnlyAndSupports(transaction);
        Assertions.assertThat(save).isNotNull();
        System.out.println(save);
    }

    @Test
    public void saveReadOnlyAndRequired() throws Exception {
        Transaction transaction = Transaction.builder().type("Exception").message("只读和Required异常测试").build();
        Transaction save = transactionService.saveReadOnlyAndRequired(transaction);
        Assertions.assertThat(save).isNotNull();
        System.out.println(save);
    }

    @Test
    public void saveAndRequired2() throws Exception {
        Transaction transaction1 = Transaction.builder().type("Exception").message("Required异常测试1").build();
        Transaction transaction2 = Transaction.builder().type("Exception").message("Required异常测试2").build();
        Transaction save = transactionService.saveAndRequired(transaction1, transaction2, transactionService);
        Assertions.assertThat(save).isNotNull();
    }

    @Test
    public void updateAndRequiredNew() throws Exception {
        Transaction transaction =
                Transaction.builder().id(BigInteger.valueOf(1000)).type("Exception").message("RequiredNew异常update测试")
                        .build();
        int result = transactionService.updateAndRequiredNew(transaction);
        Assertions.assertThat(result).isGreaterThan(0);
        System.out.println(result);
    }

    @Test
    public void saveAndRequiredNew() throws Exception {
        Transaction transaction = Transaction.builder().type("Exception").message("RequiredNew异常测试").build();
        Transaction save = transactionService.saveAndRequiredNew(transaction);
        Assertions.assertThat(save).isNotNull();
        System.out.println(save);
    }

    @Test
    public void saveAndRequiredNew2() throws Exception {
        Transaction transaction1 = Transaction.builder().type("Exception").message("RequiredNew异常测试1").build();
        Transaction transaction2 = Transaction.builder().type("Exception").message("RequiredNew异常测试2").build();
        Transaction save = transactionService.saveAndRequiredNew(transaction1, transaction2, transactionService);
        Assertions.assertThat(save).isNotNull();
    }

    @Test
    public void saveAndRequiredAndRequiredNew() throws Exception {
        Transaction transaction1 =
                Transaction.builder().type("Exception").message("RequiredAndRequiredNew异常测试1").build();
        Transaction transaction2 =
                Transaction.builder().type("Exception").message("RequiredAndRequiredNew异常测试2").build();
        Transaction save = transactionService.saveAndRequiredAndRequiredNew(transaction1, transaction2,
                transactionService);
        Assertions.assertThat(save).isNotNull();
    }

}

相关文章

网友评论

      本文标题:@Transactional

      本文链接:https://www.haomeiwen.com/subject/zkbziqtx.html