Spring事务的传播行为和隔离级别
来源:互联网 发布:伺服电机plc编程实例 编辑:程序博客网 时间:2024/06/06 09:24
事务的使用过程中,用的最多的传播行为是require,在大部分的mis系统里,可以对整个业务层切一个require的事务就可以满足需要。
但spring提供的不仅如此,对于复杂的业务,Spring也提供了相应的事务传播行为来满足业务需要。
Spring中的传播行为如下:
Require:支持当前事务,如果没有事务,就建一个新的,这是最常见的;
Supports:支持当前事务,如果当前没有事务,就以非事务方式执行;
Mandatory:支持当前事务,如果当前没有事务,就抛出异常;
RequiresNew:新建事务,如果当前存在事务,把当前事务挂起;
NotSupported:以非事务方式执行操作,如果当前存在事务,就把事务挂起;
Never:以非事务方式执行,如果当前存在事务,则抛出异常。
Nested:新建事务,如果当前存在事务,把当前事务挂起。与RequireNew的区别是与父事务相关,且有一个savepoint。
其中,Require、Supports、NotSupported、Never两个看文字也就能了解,就不多说了。而Mandatory是要求所有的操作必须在一个事务里,较Require来说,对事务要求的更加严格。
RequireNew:当一个Require方法A调用RequireNew方法B时,B方法会新new一个事务,并且这个事务和A事务没有关系,也就是说B方法出现异常,不会导致A的回滚,同理当B已提交,A再出现异常,B也不会回滚。
Nested:这个和RequireNew的区别是B方法的事务和A方法的事务是相关的。只有在A事务提交的时候,B事务都会提交。也就是说当A发生异常时,A、B事务都回滚,而当B出现异常时,B回滚,而A回滚到savepoint,如下代码所示:
- public void A(){
- //操作1
- //操作2
- //操作3
- try{
- //savepoint
- B();//一个Nested的方法
- } catch{
- //出现异常,B方法回滚,A方法回滚到
- //savepoint,也就是说操作1、2、3 都还在
- C();
- } finally{
- }
- }
说完了事务传播的行为,现在再说下事务隔离级别,事务隔离级别的出现,是为了使你在性能与数据的有效性之间做一个平衡,不是说级别越高越好,只有合适才是最好的。
事务隔离级别如下:
Serializable:最严格的级别,事务串行执行,资源消耗最大;
Repeatable Read:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。
Read Committed:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已经修改但未提交的数据。适用于大多数系统。
Read Uncommitted:保证了读取过程中不会读取到非法数据。
想要理解这四个级别,还需要知道三种不讨人喜欢的事情:
dirty reads:脏读,就是说事务A未提交的数据被事务B读走,如果事务A失败回滚,将导致B所读取的数据是错误的。
non-repeatable reads:不可重复读,就是说事务A中两处读取数据,第一次读时是100,然后事务B把值改成了200,事务A再读一次,结果就发现值变了,造成A事务数据混乱。
phantom read:幻读,和不可重复读相似,也是同一个事务中多次读不一致的问题。但是不可重复读的不一致是因为它所要取的数据集被改变了,而幻读所要读的数据不一致却不是他所要读的数据改变,而是它的条件数据集改变。比如:Select id where name="ppgogo*",第一次读去了6个符合条件的id,第二次读时,由于事务B把第一个贴的名字由"dd"改成了“ppgogo9”,结果取出来7个数据。
而事务的隔离级别会导致读取到非法数据的情况如下表示:
三、Spring事务的传播性与隔离级别
Spring它对JDBC的隔离级别作出了补充和扩展,其提供了7种事务传播行为。(通俗解释原址)
1、PROPAGATION_REQUIRED:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。
2、PROPAGATION_REQUIRES_NEW:如果没有,就新建一个事务;如果有,就将当前事务挂起。
3、PROPAGATION_NESTED:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。
4、PROPAGATION_SUPPORTS:如果没有,就以非事务方式执行;如果有,就使用当前事务。
5、PROPAGATION_NOT_SUPPORTED:如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。
6、PROPAGATION_NEVER:如果没有,就以非事务方式执行;如果有,就抛出异常。
7、PROPAGATION_MANDATORY:如果没有,就抛出异常;如果有,就使用当前事务。
第4、5、6、7种特性很好理解了,主要是前三种特性比较容易混淆或用错。
那么PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED的区别在哪呢?
什么有就创建一个什么嵌套挂起,很明显不如一些使用场景清晰,那就直接上例子。
先定义一些实验性的方法。(例子代码:https://git.oschina.net/sluggarddd/spring-tx-demo.git)
@Service
public class IUserServiceImpl implements IUserService {
@Resource
IUserDAO userDAO;
@Resource
IUserService2 userService2;
//不带事务的方法
public void funNone() throws Exception {
save(new UserEntity("zhw"));
}
//启动默认事务的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire() throws Exception {
save(new UserEntity("wlj"));
}
//启动默认事务的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire2() throws Exception {
save(new UserEntity("shifang"));
}
//启动默认事务的方法,抛出RuntimeException
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void funRequireException() throws Exception {
save(new UserEntity("max"));
throwExcp();
}
//启动嵌套事务的方法
@Transactional(propagation = Propagation.NESTED)
public void funNest() throws Exception {
save(new UserEntity("yunxinghe"));
}
//启动嵌套事务的方法,但会抛出异常
@Override
@Transactional(propagation = Propagation.NESTED)
public void funNestException() throws Exception {
save(new UserEntity("edward"));
throwExcp();
}
//REQUIRES_NEW事务的方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNew() throws Exception {
save(new UserEntity("kb"));
}
//REQUIRES_NEW事务的方法,但会抛出异常
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNewException() throws Exception {
save(new UserEntity("laura"));
throwExcp();
}
//抛出异常
private void throwExcp() throws Exception {
throw new RuntimeException("boom");
}
//保存数据
public int save(UserEntity userEntity) throws Exception {
userDAO.save(userEntity);
return userEntity.getId();
}
}
我们定义了两个镜像(就是一样的Serivce)分别是UserService,UserService2,是一样的,只是相互调用,不重复上代码。
@Override
@Transactional
public void fun1() throws Exception {
//数据库操作
funNone();
//调用另一个service的方法
userService2.funNest();
//当调用另一个Service的method的时候,想要将他的事务加到现在这个事务中,很可能自然而然想到了嵌套
//这么想就错了,Required的定义里已经说明了如果没有,就新建一个事务;如果有,就加入当前事务。
//那么直接使用Required就满足需求
//这样在方法中任何地方发生unchecked异常将触发整个方法的回滚
//而Nested的使用场景下面再介绍
}
NESTED事务使用场景
@Override
@Transactional
public void fun2() throws Exception {
//嵌套事务的使用场景
funNone();
try {
//当所调用的方法为NESTED事务,该事务的回滚可以不影响到调用者的事务
//当然如果没有catch exception,异常冒泡而出,就将触发调用者事务的回滚
userService2.funNestException();
} catch (Exception e) {
//do something
}
userService2.funRequire();
}
//执行结果:
//userService2.funNestException()被回滚
//其他插入成功
外部的异常能触发所调用的NESTED事务回滚
@Override
@Transactional
public void fun3() throws Exception {
//嵌套事务的使用场景
funNone();
try {
//调用的事务为NESTED事务的方法
userService2.funNest();
} catch (Exception e) {
//do something
}
userService2.funRequire();
//此时在调用者处,触发一个unchecked异常
throwExcp();
//此时会发现包括调用的userService2.funNest()也被回滚了
//也就是说,当调用的方法是NESTED事务,该方法抛出异常如果得到了处理(try-catch),那么该方法发生异常不会触发整个方法的回滚
//而调用者出现unchecked异常,却能触发所调用的nested事务的回滚.
}
//执行结果
//全部被回滚
REQUIRES_NEW的使用场景
@Override
@Transactional
public void fun4() throws Exception {
//而REQUIRES_NEW,当被调用时,就相当于暂停(挂起)当前事务,先开一个新的事务去执行REQUIRES_NEW的方法,如果REQUIRES_NEW中的异常得到了处理
//那么他将不影响调用者的事务,同时,调用者之后出现了异常,同样也不会影响之前调用的REQUIRES_NEW方法的事务.
//不会回滚
funNone();
try {
//当异常得到处理,外部不会触发回滚
userService2.funRequireNewException();
} catch (Exception e) {
}
}
//执行结果
//funNone()正常持久化
// userService2.funRequireNewException()回滚
@Override
@Transactional
public void fun5() throws Exception {
//数据库操作
funNone();
//调用RequireNew类型事务的方法,调用者的异常回滚不会影响到它
userService2.funRequireNew();
//数据库操作
funNone();
//抛出unchecked异常,触发回滚
throwExcp();
}
//执行结果
//userService2.funRequireNew();正常持久化
//其他操作被回滚
如果调用的是REQUIRED类型,即使处理了被调用方法抛出的异常仍然会被回滚。
@Override
@Transactional
public void fun6() throws Exception {
funNone();
try {
//当调用的是Required时,就算异常被处理了,整个方法也将会回滚
userService2.funRequireException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
//执行结果
//被回滚
总结:附上一段我觉得很好的总结(Jurgen Hoeller原话翻译)(翻译从这里拷的)
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
四、Spring事务自我调用的坑
当Spring的事务在同一个类时,它的自我调用时事务就完犊子了!(不知道这个的时候被坑出翔)
当同一个类的方法之间事务发生自我调用,其事务的特性将失效。
@Override
@Transactional
public void fun7() throws Exception {
funRequire();
try {
//本应回滚这个方法,但发生了异常并没有回滚
funNestException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
funRequire();
}
例如上面Nested的特性就没了,其运行结果是三个发生insert的语句都成功插入到数据库了。
原因我自己肯定没别人总结的好(连问题都说的不太清楚),就直接放链接了(原因点此),不想看的直接上解决方法。
1、首先引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}<ersion>
</dependency>
2、开启暴露AOP代理到ThreadLocal支持
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
3、在自我调用的时候这么写
fun8() Exception {
((IUserService) AopContext.()).funRequire();
{
((IUserService) AopContext.()).funNestException();
} (Exception e) {
System..println(e.getMessage());
}
((IUserService) AopContext.()).funRequire();
}
这样,就能让配置在该方法上的事务发挥应有的特性啦。
原因我也来总结一句,因为开启事务和事务回滚,实际这个过程是aop代理帮忙完成的,当调用一个方法时,他会先检查时候有事务,有则开启事务,当调用本类的方法是,他并没有将其视为proxy调用,而是方法的直接调用,所以也就没有检查该方法是否含有事务这个过程,那么他生命的事务也就不成立了。
另外,除了这个解决方法,开涛大神那个博客还提供了其他解决方式,可以根据自己的需求选择。
总结一下对自己理解还是蛮有帮助的,希望也能稍微帮到有需要的人。
完。
- spring事务传播行为和隔离级别
- Spring事务传播行为和隔离级别
- spring事务隔离级别和传播行为
- Spring事务隔离级别和传播行为
- spring事务传播行为和隔离级别
- Spring事务传播行为和隔离级别
- Spring 事务隔离级别和传播行为
- spring的事务的传播行为和隔离级别
- Spring 事务的隔离级别和传播行为的理解
- Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- spring的事务传播行为和隔离级别
- 浅析Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- Spring事务的传播行为和隔离级别
- 每个开发人员都应该知道的10个Linux命令
- CCF CSP认证考试历年真题 数字排序 C语言实现
- 常用正则表达式大全
- phpcms常用个调用代码
- 自定义时间选择器
- Spring事务的传播行为和隔离级别
- Linux共享内存
- nested exception is java.lang.IllegalStat eException:duplicate spring bean
- 数据库的存储过程(理解,示例,注意事项,异常分析)
- 大整数相加函数
- 微图App中如何切换普通标注和专业标注
- cronolog+awstats搭建Nginx日志分析(静态)
- Uva 10983 Buy one, get the rest free(二分+最大流)
- OpenGL学习:立方体纹理和天空包围盒(Cubemaps And Skybox)