java事务传播机制一文搞懂。

lanhy
发布于 2020-9-1 11:15
浏览
1收藏

使用:

1、引入spring的jdbc、数据库驱动,数据源

2、配置数据源,注入JdbcTemplate,启用事务管理,注入DataSourceTransactionManager

3、传播机制

@see Propagation#REQUIRED 支持当前事务,如果没有则新建一个事务,
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b新建一个事务
@see Propagation#SUPPORTS 支持当前事务,如果没有则以非事务运行
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b以非事务运行
@see Propagation#MANDATORY 支持当前事务,如果没有则抛出异常
例:a方法调用b方法,如果a方法有事务,则b加入a事务,如果a没有,则b抛出异常
@see Propagation#REQUIRES_NEW 创建一个新的事务,如果当前有事务,则挂起
例:a方法调用b方法,不论a有没有事务,b都会新建一个事务
@see Propagation#NOT_SUPPORTED 不支持事务,如果当前有事务则挂起
例:a方法调用b方法,不论a有没有事务,b都会以非事务方式运行
@see Propagation#NEVER 不支持事务,如果有则抛出异常
例:a方法调用b方法,如果a有事务,b会抛出异常
@see Propagation#NESTED 嵌套事务
例:a方法调用b方法,如果a有事务,b会开启一个内嵌事务。如果a没有事务,则b开启一个新的事务

4、代码测试

服务类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;

/**
 * @author liangjunhui
 * @date Created in 2020-08-12 11:25
 */
@Service
public class UsersService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    UsersService usersService;
    private int num = 9;

    @Transactional(rollbackFor = Exception.class,timeout = 10)
    public void requiredA(int a, int b, Propagation propagation) {
        try {
            insertUser(a);
        } finally {
            invoke(b, propagation);
        }
    }
    @Transactional(rollbackFor = Exception.class)
    public void requiredACatchB(int a, int b, Propagation propagation) {
        try {
            insertUser(a);
        } finally {
            try {
                invoke(b, propagation);
            } catch (Exception e) {
            }
        }
    }
    public void invoke(int b, Propagation propagation) {
        switch (propagation) {
            case REQUIRED:
                usersService.requiredB(b);
                break;
            case SUPPORTS:
                usersService.supportsB(b);
                break;
            case MANDATORY:
                usersService.mandatoryB(b);
                break;
            case REQUIRES_NEW:
                usersService.requiresNewB(b);
                break;
            case NOT_SUPPORTED:
                usersService.notSupportedB(b);
                break;
            case NEVER:
                usersService.neverB(b);
                break;
            case NESTED:
                usersService.nestedB(b);
                break;

            default:
                break;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void requiredB(int b) {
        insertUser(b);
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    public void supportsB(int b) {
        insertUser(b);
    }
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    public void mandatoryB(int b) {
        insertUser(b);
    }
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void requiresNewB(int b) {
        insertUser(b);
    }
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    public void notSupportedB(int b) {
        insertUser(b);
    }
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    public void neverB(int b) {
        insertUser(b);
    }
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    public void nestedB(int b) {
        insertUser(b);
    }


    /**
     * 当i为0的时候,会发生异常
     * @param i
     * @return
     */
    private int insertUser(int i) {
        String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();
        String name = methodName + new Random().nextInt();
        int update = jdbcTemplate.update("insert into users(`userName`,`passWord`) values (?,?)", new Object[]{name, "123456"});
        int res = num / i;
        return update;
    }

}

service

注意:有多个事务注解标注的方法相互调用的时候,一定要从IOC容器中取对象,否则不生效。原因是apc默认创建的对象调用目标方法是通过实例化初始化之后的bean,这个bean是未代理增强的,创建代理是BeanPostProcessor实现类负责的,通过初始化的后置处理器包装,然后将包装好的bean放入IOC容器中,这个代理对象持有未代理之前的引用,所以说要从容器中取。

测试类:

/**
 * @author liangjunhui
 * @date Created in 2020-08-12 10:58
 */
public class TestTx extends BaseTest {


    /**
     * 测试所有的事务传播,该情况调用方不处理被调用方的异常
     * 测试结果:
     * ----------------------测试REQUIRED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * 当前无事务,失败共执行1次,成功0次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试REQUIRED结束-------------------------
     * ----------------------测试SUPPORTS开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * 当前无事务,失败共执行1次,成功1次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试SUPPORTS结束-------------------------
     * ----------------------测试MANDATORY开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * 发生异常:No existing transaction found for transaction marked with propagation 'mandatory'
     * 当前无事务,失败共执行1次,成功0次
     * 发生异常:No existing transaction found for transaction marked with propagation 'mandatory'
     * 当前无事务,成功共执行1次,成功0次
     * ----------------------测试MANDATORY结束-------------------------
     * ----------------------测试REQUIRES_NEW开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功1次
     * 当前无事务,失败共执行1次,成功0次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试REQUIRES_NEW结束-------------------------
     * ----------------------测试NOT_SUPPORTED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功1次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功1次
     * 当前有事务,a失败,b成功共执行2次,成功1次
     * 当前无事务,失败共执行1次,成功1次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试NOT_SUPPORTED结束-------------------------
     * ----------------------测试NEVER开始-------------------------
     * 发生异常:Existing transaction found for transaction marked with propagation 'never'
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 发生异常:Existing transaction found for transaction marked with propagation 'never'
     * 当前有事务,a成功,b成功共执行2次,成功0次
     * 发生异常:Existing transaction found for transaction marked with propagation 'never'
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 发生异常:Existing transaction found for transaction marked with propagation 'never'
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * 当前无事务,失败共执行1次,成功1次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试NEVER结束-------------------------
     * ----------------------测试NESTED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * 当前无事务,失败共执行1次,成功0次
     * 当前无事务,成功共执行1次,成功1次
     * ----------------------测试NESTED结束-------------------------
     */
    @Test
    public void testTx(){
        for (Propagation propagation:Propagation.values()){
            System.out.println("----------------------测试"+propagation.name()+"开始-------------------------");
            testTxCommon(propagation);
            System.out.println("----------------------测试"+propagation.name()+"结束-------------------------");
        }
    }

    /**
     * 测试所有的事务传播,该情况调用方处理被调用方的异常
     * 测试结果:
     * ----------------------测试REQUIRED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 发生异常:Transaction rolled back because it has been marked as rollback-only
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * ----------------------测试REQUIRED结束-------------------------
     * ----------------------测试SUPPORTS开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 发生异常:Transaction rolled back because it has been marked as rollback-only
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * ----------------------测试SUPPORTS结束-------------------------
     * ----------------------测试MANDATORY开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 发生异常:Transaction rolled back because it has been marked as rollback-only
     * 当前有事务,a成功,b失败共执行2次,成功0次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * ----------------------测试MANDATORY结束-------------------------
     * ----------------------测试REQUIRES_NEW开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功1次
     * 当前有事务,a失败,b成功共执行2次,成功1次
     * ----------------------测试REQUIRES_NEW结束-------------------------
     * ----------------------测试NOT_SUPPORTED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功1次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功2次
     * 当前有事务,a失败,b成功共执行2次,成功1次
     * ----------------------测试NOT_SUPPORTED结束-------------------------
     * ----------------------测试NEVER开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功1次
     * 当前有事务,a成功,b失败共执行2次,成功1次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * ----------------------测试NEVER结束-------------------------
     * ----------------------测试NESTED开始-------------------------
     * 当前有事务,a失败,b失败共执行2次,成功0次
     * 当前有事务,a成功,b成功共执行2次,成功2次
     * 当前有事务,a成功,b失败共执行2次,成功1次
     * 当前有事务,a失败,b成功共执行2次,成功0次
     * ----------------------测试NESTED结束-------------------------
     */
    @Test
    public void testTxCacheB(){
        for (Propagation propagation:Propagation.values()){
            System.out.println("----------------------测试"+propagation.name()+"开始-------------------------");
            testTxCommonCacheB(propagation);
            System.out.println("----------------------测试"+propagation.name()+"结束-------------------------");
        }
    }

    private void testTxCommon(Propagation propagation) {
        AllStatus[] values = AllStatus.values();
        for (AllStatus status : values) {
            String[] strArr = status.getStatus().split("");
            int before = testCount();
            int a = Integer.parseInt(strArr[0]);
            try {
                if (strArr.length > 1) {
                    int b = Integer.parseInt(strArr[1]);
                    usersService.requiredA(a, b, propagation);
                } else {
                    usersService.invoke(a, propagation);
                }
            }catch (ArithmeticException ae){
                // e.printStackTrace();
            }catch (Exception e) {
                System.out.println("发生异常:"+e.getMessage());
            }
            int after = testCount();
            System.out.println(status.getMsg() + "共执行" + strArr.length + "次,成功" + (after - before) + "次");

        }
    }

    private void testTxCommonCacheB(Propagation propagation) {
        AllStatus[] values = AllStatus.values();
        for (AllStatus status : values) {
            String[] strArr = status.getStatus().split("");
            int before = testCount();
            int a = Integer.parseInt(strArr[0]);
            try {
                if (strArr.length > 1) {
                    int b = Integer.parseInt(strArr[1]);
                    usersService.requiredACatchB(a, b, propagation);
                } else {
                    continue;
                }
            }catch (ArithmeticException ae){
                // e.printStackTrace();
            }catch (Exception e) {
                System.out.println("发生异常:"+e.getMessage());
            }
            int after = testCount();
            System.out.println(status.getMsg() + "共执行" + strArr.length + "次,成功" + (after - before) + "次");

        }
    }

    public int testCount() {
        JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
        Map<String, Object> countMap = jdbcTemplate.queryForMap("select count(1) as count from users");
        return Integer.parseInt(countMap.get("count").toString());
    }

    @Before
    public void before() {
        getContext(TxConfig.class);
        usersService = context.getBean(UsersService.class);
    }

    private UsersService usersService;
    enum AllStatus {
        ALL_FAILE("00", "当前有事务,a失败,b失败"),
        ALL_SUCCESS("11", "当前有事务,a成功,b成功"),
        SUCCESS_FAILE("10", "当前有事务,a成功,b失败"),
        FAILE_SUCCESS("01", "当前有事务,a失败,b成功"),
        FAILE("0", "当前无事务,失败"),
        SUCCESS("1", "当前无事务,成功"),;
        private String status;
        private String msg;

        AllStatus(String status, String msg) {
            this.status = status;
            this.msg = msg;
        }

        public String getStatus() {
            return status;
        }

        public String getMsg() {
            return msg;
        }
    }
}

测试类

5、REQUIRED、REQUIRES_NEW、NESTED 之间的区别:

REQUIRED是a,b公用一个事务,a、b任一一个发生异常,事务都会回滚,即使是a未发生异常且处理了b的异常;
NESTED是a、b为父子事务,如果b发生了异常,a处理了b的异常,这样只有b会回滚,如果a发生了异常,则回滚。
父影响子,子不影响父。
REQUIREs_NEW是a、b只会影响到自己,即只要a或b执行完没有发生异常,事务就回提交。

 

 

分类
标签
已于2020-9-2 18:14:02修改
1
收藏 1
回复
举报
回复
    相关推荐