关于Spring事务的传播特性

摘要:Spring事务管理基于底层数据库本身的事务处理机制,对数据库事务操作的一次封装,相当于把使用JDBC代码开启、提交、回滚事务进行了封装。其传播特性共有七个

正文:

事务的传播特性

  1. Propagation.REQUIRED

    方法被调用时自动开启事务,在事务范围内使用则使用同一个事务,如果当前线程中已经存在事务, 方法调用会加入此事务, 如果当前没有事务,就新建一个事务。

  2. Propagation.REQUIRES_NEW

    无论何时自身都会开启事务,这个事务不依赖于外部事务,它拥有自己的隔离范围,自己的锁,等等。当内部事务开始执行时,外部事务将被挂起,内部事务结束时,外部事务将继续执行。

  3. Propagation.SUPPORTS

    自身不会开启事务,在事务范围内使用挂起事务,运行完毕不使用事务

  4. Propagation.NOT_SUPPORTED

    自身不会开启事务,在事务范围内使用挂起事务,运行完毕恢复事务

  5. Propagation.MANDATORY

    自身不会开启事务,必须在事务环境使用否则报错

  6. Propagation.NEVER

    自身不会开启事务,在事务范围内使用抛出异常

  7. Propagation.NESTED

    如果当前存在事务,则在嵌套的事务中执行,如果没有则按照TransactionDefinition.PROPAGATION_REQUIRED 属性执行。可以认为是已经存在事务的一个真正的子事务。嵌套事务开始执行时,它将取得一个 save point。如果这个嵌套事务失败,我们将回滚到此save point。嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。

    Propagation.REQUIRES_NEW和Propagation.NESTED 的最大区别在于,Propagation.REQUIRES_NEW完全是一个新的事务,而 Propagation.NESTED 则是外部事务的子事务。如果外部事务 commit,嵌套事务也会被 commit,这个规则同样适用于rollback。

测试代码

因为业务的需要,业务逻辑优先在单个Service中实现,同样为了保证数据的一致性需要使用事务,在单个Service中实现会出现嵌套事务。

如我们使用service C同时调用service A和service B ,如果service B抛出异常那么service C(外部事务)如果没有特殊配置(如异常时事务提交)那么整个事务是一定会rollback的。

测试代码:
Service C:
如下代码所示,使用声明式注解@Transactional(rollbackFor=Exception.class)默认传播特性是Propagation.REQUIRED。

1
2
3
4
5
6
7
@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)
@Override
public int insert(Users users) throws Exception{
int i = users1Service.insert(users);
int j = users2Service.insert1(users);
return i+j;
}

Controller层:

1
2
3
4
5
6
7
@RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseInfo postUser1(@RequestBody Users user) throws Exception {
ResponseInfo resInfo = new ResponseInfo();
int i = usersService.insert(user);
resInfo.setResponseInfo("users一共新增了" + i + "条数据");
return resInfo;
}

Service A:

1
2
3
4
5
@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRES_NEW)
@Override
public int insert(Users users) throws Exception{
return usersMapper.insert(users);
}

Service B:

1
2
3
4
5
@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRED)
@Override
public int insert1(Users users) throws Exception{
return usersMapper.insert1(users);
}

根据以上代码可以通过测试两张表同时插入而一张表失败最后返回的数据进行分析:
本次使用Postman测试工具,数据库oracle 11g。
两张表其中一张age字段为number6位,一张为number2位,测试数据为三位,会有一张表插入失败,测试数据如下图所示。
测试数据

结果是两张表都没有插入,如下图所示。
测试结果

经过测试,说明嵌套事务与事务的传播特性有关,都使用默认的传播属性REQUIRED第一张插入后,第二张失败会导致外部事务(Service C)rollback,保证了数据的一致性。
若内部事务有使用REQUIRES_NEW属性,则会单独开一事务其运行结果不会影响外部数据会出现数据不一致。
若内部事务有使用NESTED属性,内部事务如果出现异常则会rollback到save point,从而外部事务可以使用try-catch进行分支执行(try里执行Service A,catch里执行Service B)。
查询语句应该设置为read-only,传播范围设置为NOT_SUPPORTED
如下代码所示:

1
2
3
4
5
6
7
8
9
10
/**
* {@inheritDoc}
* {@link newframe.business.demo.service.SecurityInfoService#queryAll()}
* */
@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
@Override
public List<SecurityInfo> queryAll() throws MessageException {
List<SecurityInfo> list = securityInfoMapper.queryAll();
return list;
}

具体使用哪个属性根据业务来进行选择。

Spring注解

在项目中如果大量组件采用xml的bean定义来配置,显然会增加配置文件的体积,查找不太方便。而注解可以很方便的标注完将其放入spring容器管理。

业务层注解
@Service:用于标注业务层组件。
@Controller:用于标注控制层组件。
@Repository:用于标注数据访问组件,即Dao组件。
@Component:泛指组件,不容易归类可以使用该组件。

Bean容器相关的注解
@Autowired:等同autowire=byType,根据类型的自动注入依赖。
@Qualifier:等同autowire=byName,当@Autowired注解需要判断多个 bean类型相同时,就需要使用@Qualifier(“xxBean”)来指定依赖的bean 的id。
@Resource:属于JSR250标准,作用同@Autowired,是属于byName类 型的依赖注入,使用方式:@Resource(name=”xxBean”),不带参数是默 认类名首字母小写。

@RequestBody:用于读取request请求的body部分数据(Json串或XML数据),将其转化为需要的对象。
@ResponseBody:将Controller的方法返回的对象通过适当的转换(通过配置可以返回Json或XML数据),写入response对象的body数据区。新框架可以在Controller里使用该注解返回Json数据。