Spring-事务的传播属性

Spring-事务的传播属性(六)
事务的传播属性概述

所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义,具体常量的解释见下:

  • Propagation.REQUIRED(required):支持当前事务,如果当前有事务, 那么加入事务, 如果当前没有事务则新建一个(默认情况)
  • Propagation.NOT_SUPPORTED(not_supported) : 以非事务方式执行操作,如果当前存在事务就把当前事务挂起,执行完后恢复事务(忽略当前事务);
  • Propagation.SUPPORTS (supports) :如果当前有事务则加入,如果没有则不用事务。
  • Propagation.MANDATORY (mandatory) :支持当前事务,如果当前没有事务,则抛出异常。(当前必须有事务)
  • PROPAGATION_NEVER (never) :以非事务方式执行,如果当前存在事务,则抛出异常。(当前必须不能有事务)
  • Propagation.REQUIRES_NEW (requires_new) :支持当前事务,如果当前有事务,则挂起当前事务,然后新创建一个事务,如果当前没有事务,则自己创建一个事务。
  • Propagation.NESTED (nested 嵌套事务) :如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行(和required一样)。嵌套的事务使用保存点作为回滚点,当内部事务回滚时不会影响外部事物的提交;但是外部回滚会把内部事务一起回滚回去。(这个和新建一个事务的区别)

事务开启前奏:入库后抛异常与正常入库

public class UserInfoDaoImpl implements UserInfoDao {

    private JdbcTemplate jdbcTemplate;

    public Integer save(UserInfoVo userInfoVo) {
        SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate)
                .withTableName("user_info").usingColumns("user_name", "age").usingGeneratedKeyColumns("id");
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("user_name", userInfoVo.getUserName());
        params.put("age", userInfoVo.getAge());
        int id = insert.executeAndReturnKey(params).intValue();

        throw new RuntimeException("测试异常");

    }

    public Integer save2(UserInfoVo userInfoVo) {
        SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate)
                .withTableName("user_info").usingColumns("user_name", "age").usingGeneratedKeyColumns("id");
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("user_name", userInfoVo.getUserName());
        params.put("age", userInfoVo.getAge());
        int id = insert.executeAndReturnKey(params).intValue();
        //throw new RuntimeException("测试异常");
        return id;

    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}
required有则有无则创建(默认)

说明: 如果当前已经存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。

//service1中

public class UserInfoExtendServiceImpl{


    @Transactional(propagation = Propagation.REQUIRED)
    public void serviceA() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(100);
        infoVo.setUserName("ceshi1");
        userInfoDao.save(infoVo);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void serviceB() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(200);
        infoVo.setUserName("ceshi2");
        userInfoDao.save2(infoVo);
    }
}

//sercie2中:
public class UserInfoServiceImpl{
    private UserInfoExtendService userInfoExtendService;
    public void setUserInfoExtendService(UserInfoExtendService userInfoExtendService) {
        this.userInfoExtendService = userInfoExtendService;
    }


@Transactional
    public void service() {
        userInfoExtendService.serviceA();
        userInfoExtendService.serviceB();
    }    
}

说明:默认情况下,propagation=PROPAGATION_REQUIRED,整个service调用过程中,只存在一个共享的事务,当有任何异常发生的时候,所有操作回滚。

sercieA,serviceB,service,他们三个将为同一个事务。

如果当前有事务则加入事务中,如果当前没有事务则自己创建一个事务,上面的例子中service中有事务了,serviceA,serviceB中自己就不会创建事务了,而是service,serviceA,serviceB为一个事务。

如果service中没有事务,则sercieA,serviceB会各自创建一个事务,互不影响哦!

“当前事务”:对于serviceA来说,当前事务就是sercie里面的事务,相当于调用sercieA的那个方法,如果调用它之前就有事务了,则…..

错误提示

@Transactional(propagation = Propagation.REQUIRED)
    public void serviceA() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(100);
        infoVo.setUserName("ceshi1");
        userInfoDao.save(infoVo);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void serviceB() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(200);
        infoVo.setUserName("ceshi2");
        userInfoDao.save2(infoVo);
    }

    @Transactional
    public void service() {
        serviceA();
        serviceB();
    }

当时为了写demo所以就写成上面的样子了,导致后面几个事务测试都无效;要知道spring aop内部方法调用会丢失代理的哦。service,serviceA,serviceB在一个类里面,service调用serviceA,serviceB,会产生serviceA,serviceB上面的事务无效,只有service有效。这设计到spring AOP内部调用的问题,会单独拿出一片文章来讲解的。

可以参考:Spring之Aop方法内部调用问题

Spring Aop之对象内部方法间的嵌套失效

supports有则有无则无事务

如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。

//Sercie1里面:

/**
 * 如果当前有事务则加入事务中,如果没有则什么都不做,相当于没事务。
 */
@Transactional(propagation = Propagation.SUPPORTS)
    public void serviceC() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1000);
        infoVo.setUserName("ceshi2");
        userInfoDao.save(infoVo); //没有被回滚哦
    }

//Sercie2里面:
    //@Transactional,开启事务后,serviceC会和C1公用一个事务,如果这里没有开启,则serviceC不会自己创建事务。
    public void serviceC1() {
        Sercie1.serviceC();
    }

说明:

C1在调用C的过程中

1. C1没有事务,则C也没有事务。

2. C1有事务,则C加入到C1的事务中。

not_supported忽略事务

NOT_SUPPORTED:这个的意思是一直处于无事务状态中执行,如果当前有事务则忽略事务,自己处在一个无事务中执行。

和上面正好反正,和never事务不一样哦,不会跑异常,自己只是安静的做事。

mandatory必须有

当前必须存在一个事务,否则抛出异常。

//Sercie1中:

    //Propagation.MANDATORY当前必须存在一个事务,否则抛出异常。
    @Transactional(propagation = Propagation.MANDATORY)
    public void serviceD() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(200);
        infoVo.setUserName("D");
        userInfoDao.save(infoVo);
    }

    public void serviceE() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(100);
        infoVo.setUserName("E");
        userInfoDao.save2(infoVo);
    }

//Sercie2中
    //这里调用
    public void serviceDE() {
        Sercie1.serviceE();//E正常入库了,
        Sercie1.serviceD();//D必须要有事务,不然则抛异常了。servcieDE上面没有事务,所有抛异常了。
    }
never 必须没有

如果当前存在事务,则抛出异常,否则在无事务环境上执行代码。

//Sercie1中:

    //Propagation.NEVER当前必须没有事务,否则抛出异常。
    @Transactional(propagation = Propagation.NEVER)
    public void serviceD() {
        System.out.println("执行到了这里。。。。");
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(200);
        infoVo.setUserName("D");
        userInfoDao.save(infoVo);
    }

    public void serviceE() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(100);
        infoVo.setUserName("E");
        userInfoDao.save2(infoVo);
    }

//Sercie2中
    //这里调用
    public void serviceDE() {
        Sercie1.serviceE();//E正常入库了,
        Sercie1.serviceD();//D必须要没有事务,不然则抛异常了。
    }

说明:servcieDE上面没有事务,D正常以无事务的方式执行,

sercieDE上面有事务,D抛出异常,sercieD里面直接不回执行就已经往外抛出异常了。D有异常后,如果不处理则会连带sercieE也会滚,别忘记了serviceDE中事务。

requires_new新事务

REQUIRES_NEW新建一个事务,不管当前有没有事务,都新建一个独立的事务。

如果当前存在事务,则把当前事务挂起,自己新创建一个事务,新事务执行完毕后再唤醒当前事务;两个事务没有依赖关系,可以实现自己新事务回滚了,但外部事务继续执行;外部事物回滚也不会影响到新事物的的提交。就是双方互不影响。

//Sercie1类中
/**
     * REQUIRES_NEW新建一个事务,不管当前有没有事务,都新建一个独立的事务。
     * 这里面serviceFG创建了一个事务,然后serviceF也创建了一个事务,他们互相独立;
     * 当前方法必须在自己的事务里运行,如果当前存在一个事务,则挂起该事务,这个事务执行完毕后,再唤醒挂起的事务。
     * 挂起事务使用suspend方法将原事务挂起。
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void serviceF() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1000);
        infoVo.setUserName("F2");
        userInfoDao.save(infoVo); //这个里面有一个异常哦!
    }

    public void serviceG() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1000);
        infoVo.setUserName("G2");
        userInfoDao.save2(infoVo);
    }

//Sercie2中

    @Transactional
    public void serviceFG() {
        Sercie1.serviceG();

        try {
            Sercie1.serviceF();
        } catch (Exception e) {
            //e.printStackTrace();
        }
        Sercie1.serviceH();
    }

注意虽然sercieF新建了一个事务,但是如果serviceF抛出异常,还是需要捕获 不然serviceFG里面里面发现有异常抛出,就会把serviceG也给回滚了。

sercieFG有事务:

1. sercieG()提交成,serviceF新建一个事务,serviceF内部有错,则自己回滚自己的;serviceFG捕获了sercieF的异常则不会影响到serviceFG的事务提交。

如果不处理serviceF中的异常,serviceFG发现异常会回滚自己的事务,这个时候serviceG也会被回滚。

2. serviceG()正常执行,sercieF()正常执行后,自己就提交事务了;serviceH抛异常了,会导致serviceG和serviceH中的数据库操作回滚,但是不会影响到serviceF中的提交。

sercieFG中无事务

sercieG(),sercieH该咋的就咋的,不存在回滚的情况,也不会影响到serviceF;serviceF会自己新建一个事务,自己处理自己内部的事。

nested嵌套事务

如果当前存在事务,则使用 SavePoint 技术把当前事务状态进行保存,然后底层共用一个连接,当NESTED内部出错的时候,自行回滚到 SavePoint这个状态,只要外部捕获到了异常,就可以继续进行外部的事务提交,而不会受到内嵌业务的干扰,但是,如果外部事务抛出了异常,整个大事务都会回滚。

如果没有,则自己新建一个事务自己处理。

//Sercie1
    /**
     * 嵌套事务,如果当前有事务则设置保存点,没有则新启一个事务
     */
    @Transactional(propagation = Propagation.NESTED)
    public void serviceH() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1000);
        infoVo.setUserName("H");
        userInfoDao.save(infoVo); //这个里面有一个异常哦!
    }

    public void serviceI() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1000);
        infoVo.setUserName("I");
        userInfoDao.save2(infoVo);
    }

//Sercie2
    @Transactional
    public void serviceHI() {
        Sercie1.serviceI();
        try {
            Sercie1.serviceH();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果当前调用方有事务

1)serviceH方法内部报错,则只会回滚serviceH里面的。

2)serviceH方法内部不报错,但是外面的调用方报错了,则serviceH会跟着一起回滚。

3)serviceH方法内部不报错,外面也不报错,则serviceH和外面事务一起提交。

如果当前没有事务

serviceH就相当于一个自己新建了一个事务,和外面没有关系了,它内部就一个独立事务了。

在一个事务里面再嵌套一个事务,嵌套的事务就是在一个当前事务中设置一个保存点,保存点内部事务报错,则回滚保存点内部事务,不影响外面的。

与事务中新建事务的区别:在当前事务中新建事务,如果新事务中报错内部会回滚不影响外面的事务,外面的事务报错了,外面事务的回滚不会把新事务中的事务给回滚掉,而嵌套事务则会跟着外部事务一起回滚。

异常被捕获不回滚
userInfoDao.save(UserInfoVo userinfovo){
    int id = insert.executeAndReturnKey(params).intValue();//插入数据库
    throw new RuntimeException("测试异常");
}

@Transactional(rollbackFor = Exception.class)
    public void serviceI() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(102222);
        infoVo.setUserName("I");
        try {
            userInfoDao.save(infoVo); //这个里面有一个异常哦!
        } catch (Exception e) { //异常被catch住了,事务不会回滚。
            e.printStackTrace();
        }
    }

userInfoDao.save(infoVo);这个里面有个异常,抛异常后,被cry catch住了,所以这个事物不会回滚,数据还是插入数据库了。

异常被catch住了,就相当于没有异常了。

多线程事务传播性失效
@Transactional(propagation = Propagation.NESTED)
    public void serviceH() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(10020);
        infoVo.setUserName("H");
        userInfoDao.save2(infoVo); //这个里面没有异常
}

public void serviceI() {
        UserInfoVo infoVo = new UserInfoVo();
        infoVo.setAge(1012);
        infoVo.setUserName("I");
        userInfoDao.save(infoVo);  //这个里面有异常
}    

@Transactional
    public void serviceHI() {
        System.out.println("HI......");

        new Thread(new Runnable() {
            public void run() {
                userInfoExtendService.serviceH();  //嵌套事务
            }
        }).start();
        //serviceI抛异常了,按理说serviceH也应该跟着一起回滚的,但是由于serviceH开启了一个独立的线程,所以serviceH已经和serviceI不是同一个事务了
        //serviceI抛异常了,serviceH不会跟着回滚
        userInfoExtendService.serviceI();
    }

前面介绍过嵌套事务,如果serviceH嵌套事务没有异常,serviceI有异常,他们是需要一起回滚的;serviceH有异常,serviceI没有异常,则serviceH自己回滚自己,serviceI继续提交。这里serviceI抛异常了,按理serviceH也应该一起回滚的,但是由于serviceH开启了一个独立的线程,所以serviceH已经和serviceI不是同一个事务了。事务的传播性也就断了。

注意:事务必须在同一个线程中才有效,serviceI与serviceH不在同一个线程中,他们之间就没有事务关系了。各自为政,各自提交各自的,自个为一个独立的事务。

原因很简单,spring事务一个线程绑定一个数据库session,在该线程的数据库session中修改数据库的事务属性,改为手动提交。如果不同线程则为不同的数据库session了,不同session是互相隔离的,所以serviceI与serviceH他们两个是两个线程,也就导致了最后操作数据库是两个session了。

当然,serviceI本身还是有事务特性的,serviceH本身也还有事务特性的。只是serviceI与serviceH不在是一个事务而已了。

总结:

上面说了很多事务,记住,只要在同一个事务里面要么都成功要么都失败。遇到如:Propagation.NEVER有事务则抛出异常,如果不处理异常,它上层的事务也会因为接收到了一场而导致事务回滚。

数据库隔离级别
spring的事务隔离级别
操作名称 说明 级别
@Transactional(isolation = Isolation.DEFAULT) 默认隔离级别,和数据库的中的4种对应,数据库中用啥,spring事务隔离就用啥 -1
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读, 不可重复读,幻读),基本不使用 1
@Transactional(isolation = Isolation.READ_COMMITTED)(SQLSERVER默认) 读取已提交数据(会出现不可重复读和幻读) 2
@Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读) 4
@Transactional(isolation = Isolation.SERIALIZABLE) 串行化 8
数据库的事务隔离级别

05A35806-EBC5-8314-DD49-A5B114666997.png

  • 脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
  • 不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次读操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
  • 幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
  • 串行化:不会出现任何问题。只能一个事务提交后,另外一个事务才能开始。最安全,但效率也是最差的。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

大多数的数据库默认隔离级别为 1,比如 SqlServer、Oracle

少数数据库默认隔离级别为:2 比如: MySQL InnoDB

参考地址:

Spring事务-说说Propagation及其实现原理

http://www.uml.org.cn/j2ee/201610201.asp

Spring使用注解方式进行事务管理

收藏 (0)
评论列表
正在载入评论列表...
我是有底线的
为您推荐
    暂时没有数据