Spring事务的传播行为
程序员文章站
2022-05-06 21:37:47
...
Spring的传播行为确定了客户端和被调用端的事务边界,简单来说就是多个具有事务控制的Service的相互调用时所形成的复杂的事务边界控制。Spring定义了7种传播行为,定义的枚举代码如下:
public enum Propagation {
/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
* PROPAGATION_SUPPORTS is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p>Note: Actual transaction suspension will not work out-of-the-box on
* all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: Actual transaction suspension will not work on out-of-the-box
* on all transaction managers. This in particular applies to JtaTransactionManager,
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard J2EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* Execute within a nested transaction if a current transaction exists,
* behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
* <p>Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager when working on a JDBC 3.0 driver.
* Some JTA providers might support nested transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
7种传播行为
NESTED与REQUIRES_NEW非常类似,如果调用者不存在一个事务则会开启一个新事务。如果存在一个事务时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚,两个事务互不影响,它不是一个真正的嵌套事务。同时需要JTA事务管理器的支持。使用NESTED时,外层事务的回滚可以引起内层事务的回滚,而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
实例代码
新建三张数据表,教师信息表、学生信息表和学生评价表:
CREATE TABLE `Teacher` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`tno` int(11) NOT NULL COMMENT '工号',
`name` varchar(10) NOT NULL DEFAULT '' COMMENT '姓名',
`CreateTime` datetime NOT NULL COMMENT '记录插入时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_idx_tno` (`tno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Student` (
`ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`Sno` int(11) NOT NULL COMMENT '学号',
`Name` varchar(10) NOT NULL DEFAULT '' COMMENT '姓名',
`Sex` tinyint(2) DEFAULT NULL COMMENT '0:女,1:男',
`Grade` varchar(10) NOT NULL DEFAULT '' COMMENT '年级',
`Clazz` tinyint(2) NOT NULL COMMENT '班级',
`CreateTime` datetime NOT NULL COMMENT '记录插入时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`ID`),
UNIQUE KEY `unix_no` (`Sno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `Evaluate` (
`ID` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`Sno` int(11) DEFAULT NULL COMMENT '学号',
`Name` varchar(10) NOT NULL DEFAULT '' COMMENT '姓名',
`Comment` text NOT NULL COMMENT '评论',
`Tno` int(11) DEFAULT NULL COMMENT '老师工号',
`CommentDate` varchar(10) NOT NULL DEFAULT '' COMMENT '评论时间',
`CreateTime` datetime NOT NULL COMMENT '记录插入时间',
`UpdateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
REQUIRED和REQUIRED_NEW
创建一个方法,老师给学生添加评论的方法,实例代码如下:
@Transactional(propagation = Propagation.REQUIRED)
public boolean addEvaluateByTeacher(TeacherDO teacherDO, StudentDO studentDO, EvaluateDO evaluateDO) {
Integer tno = teacherDao.insert(teacherDO);
Integer sno = 0;
try {
sno = studentDao.insert(studentDO);
} catch (Exception e) {
}
evaluateDO.setSno(sno);
evaluateDO.setTno(tno);
return evaluateDao.insert(evaluateDO) > 0 ? true : false;
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public Integer insert(StudentDO studentDO) {
if (studentDO == null) {
LOGGER.error("insert student has error,param is null");
return 0;
}
studentDOMapper.insert(studentDO);
throw new IllegalArgumentException();
}
测试代码如下:
@Test
public void addEvaluateByTeacher() throws Exception {
TeacherDO teacherDO = new TeacherDO();
teacherDO.setTno(5);
teacherDO.setName("易中天");
teacherDO.setCreateTime(new Date());
StudentDO studentDO = new StudentDO();
studentDO.setName("王晓鹏");
studentDO.setSno(1111);
studentDO.setClazz(5);
studentDO.setSex(1);
studentDO.setGrade("三年级");
studentDO.setCreateTime(new Date());
EvaluateDO evaluateDO = new EvaluateDO();
evaluateDO.setName("王晓鹏");
evaluateDO.setComment("好好学习,天天向上");
evaluateDO.setCreateTime(new Date());
evaluateDO.setCommentDate("2017-10-01");
evaluateService.addEvaluateByTeacher(teacherDO, studentDO, evaluateDO);
}
执行结果如下:学生信息的insert方法由于使用“REQUIRES_NEW”的事务传播级别,执行时会创建新的事务,该事务和教师添加学生信息以及评论信息的方法addEvaluateByTeacher事务不是同一个事务,当学生信息的insert方法发生异常回滚时会单独回滚,不影响addEvaluateByTeacher。所以,教师信息表和教师评论表可以插入成功,而学生信息表无法插入成功。
MANATORY
此时我们修改上述的代码,学生信息的insert方法如下:
@Transactional(propagation = Propagation.MANDATORY)
public Integer insert(StudentDO studentDO) {
if (studentDO == null) {
LOGGER.error("insert student has error,param is null");
return 0;
}
return studentDOMapper.insert(studentDO);
}
教师添加学生评论信息代码如下:
@Override
public boolean addEvaluateByTeacher(TeacherDO teacherDO, StudentDO studentDO, EvaluateDO evaluateDO) {
Integer tno = teacherDao.insert(teacherDO);
Integer sno = 0;
sno = studentDao.insert(studentDO);
evaluateDO.setSno(sno);
evaluateDO.setTno(tno);
return evaluateDao.insert(evaluateDO) > 0 ? true : false;
}
此时执行单元测试会发生异常,因为事务传播级别MANDATORY的方法必须要求处于一个事务中,如果没有则会抛出异常。数据表中只有教师的信息插入成功,由于执行插入学生信息时抛出了异常,评论信息也会插入失败。执行结果如下:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:359)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:463)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy19.insert(Unknown Source)
at com.jj.tools.service.impl.EvaluateServiceImpl.addEvaluateByTeacher(EvaluateServiceImpl.java:32)
at com.jj.tools.service.EvaluateServiceImplTest.addEvaluateByTeacher(EvaluateServiceImplTest.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
关于事务的传播行为,暂时就写到这里,其他的传播行为读者可以一一去验证。在实际工作中,读者可以根据自己真实的业务场景去使用,Spring的事务传播行为在开发中占据了很重要的地位。上一篇: spring事务的传播行为
下一篇: 二叉搜索树与双向链表