zoukankan      html  css  js  c++  java
  • Java之事务的基本应用

    基本介绍

      事务是数据一致性最基本的保证,也就是说一个事务中的操作要么都成功,要么都失败,不允许部分成功。我们常说的事务就是jdbc事务,当然Java中还有其他事务,并且在使用jdbc事务有很多注意点,请详细了解“注意点”中的内容。但是这里有个误区,因为我们一般是使用spring的注解@Transactional来实现事务,所以很多人会认为spring提供了事务,其实spring本身并没有提供事务,它只是对jdbc的事务进行了封装,然后通过AOP动态代理来实现事务的功能,这样简化了jdbc事务调用的相关步骤,让我们更专注于业务功能。

      注意关键词“动态代理”,这意味着要生成一个代理类,那么我们就不能在一个类内直接调用事务方法,否则无法代理,而且该事务方法必须是public,如果定义成 protected、private 或者默认可见性,则无法调用!

    实现原理

    注意点(容易引起事务不生效)

    • 使用的数据库引擎是否支持事务
    • 加事务的方法必须是public,否则事务不起作用(这一点由Spring的AOP特性决定的,理论上而言,不public也能切入,但spring可能是觉得private自己用的方法,应该自己控制,不应该用事务切进去吧)。另外private 方法, final 方法 和 static 方法不能添加事务,加了也不生效
    • 处理的业务和事务的入口必须在一个线程内,否则事务不生效
    • 对于jdbc事务而言,必须是一个connection中才有效的
    • Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚(至于为什么spring要这么设计:因为spring认为Checked的异常属于业务的,coder需要给出解决方案而不应该直接扔该框架)。如果业务需要,一定要抛出checked异常的话,可以通过rollbackFor属性指定异常类型即可。
    • 确认调用的类是否被代理了
    • 在本类中调用另一个带有事务的方法,事务时不生效的。这个时最容易犯的错误,不生效的原因主要是和事务的实现原理有关
    • @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />备注:本系列所有博文的讨论都针对于springboot而不再对spring做说明
    • 事务注解不能注到接口上,要写到具体类上,否者不生效

    类型

    • JDBC事务
    • JAT事务
    • 容器事务

    特性

    • 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做,要么全部做。
    • 一致性:数据不会因为事务的执行而遭到破坏。
    • 隔离性:一个事务的执行,不受其他事务(进程)的干扰。既并发执行的个事务之间互不干扰。
    • 持久性:一个事务一旦提交,它对数据库的改变将是永久的。

    7种传播属性

      

    • PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
    • PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
    • PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
    • PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。会启动一个独立的新事务,这个事务将被完全 commited 或 rollback 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行
    • PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
    • PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。会开始一个 "嵌套的" 事务,它是已经存在事务的一个真正的子事务。 嵌套事务开始执行时,它将取得一个savepoint,如果这个嵌套事务失败,将回滚到此savepoint。 嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交,这个规则同样适用于rollback
    前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。 它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)

    基本场景

    图一:事务不生效:.@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的(这就是业界老问题:类内部方法调用事务不生效的问题原因)。

     

    图二:事务生效

    图三:事务生效

     

    图四:事务生效

     

    图五:事务生效(稍微解释一下,这里虽然是方法内部调用,但是事务切入了addInfo方法,所以即使内部抛出异常,也是可以生效的。当年我竟然惊讶,看来还是太年轻,哈哈)

     

    图六:事务不生效(准确的说这叫没有事务)

    图七:如何使本类中调用方法的事务生效。

      这是我们解决方法内部调用事务不生效的最常用方法之一:内部维护一个注入自己的Bean,然后使用这个属性来调用方法。其实还有一种方法,那就是利用Aop上下文来获取代理对象(((TestService)AopContext.currentProxy()).create(); ),然后通过代理对象来调用。这里需要注意:Aop上下文spring默认是关闭的,需要手动开启:<aop:aspectj-autoproxy expose-proxy="true"/>

     

    嵌套事务

     七种事务传播属性的应用

    1: REQUIRED
    加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务:
      比如说,DemoServiceB.demoMethodB的事务级别定义为REQUIRED, 那么由于执行DemoServiceA.demoMethodA的时候,DemoServiceA.demoMethodA已经起了事务,这时调用DemoServiceB.demoMethodB,
    DemoServiceB.demoMethodB看到自己已经运行在DemoServiceA.demoMethodA的事务内部,就不再起新的事务。而假如DemoServiceA.demoMethodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。 这样,在DemoServiceA.demoMethodA或者在DemoServiceB.demoMethodB内的任何地方出现异常,事务都会被回滚。即使DemoServiceB.demoMethodB的事务已经被提交,但是DemoServiceA.demoMethodA在接下来fail要回滚,DemoServiceB.demoMethodB也要回滚
      REQUIRED保证其处理过程同一个事务,如果调用的同一个类的配置的REQUIRED的方法,且此方法存在TRY CATCH代码块, 如果此代码块出现异常,程序可以继续执行。这其实使因为同类调用的方法的事务根本不生效。
      但如果调用的其他类的配置REQUIRED方法,且TRY CATCH住,则全部的提交全部回滚,且报出异常:Transaction rolled back because it has been marked as rollback-only因为事务报出异常后要全部回滚,包括父类的调用。
      如果service中包含多个dao的方法,其都属于同一个事务,其中报错全部回滚,try catch住不影响程序代码的继续执行.
    2: SUPPORTS
    如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
    3: MANDATORY
    必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
    4: REQUIRES_NEW
    这个就比较绕口了。 比如我们设计DemoServiceA.demoMethodA的事务级别为REQUIRED,DemoServiceB.demoMethodB的事务级别为REQUIRES_NEW,
    那么当执行到DemoServiceB.demoMethodB的时候,DemoServiceA.demoMethodA所在的事务就会挂起,DemoServiceB.demoMethodB会起一个新的事务,等待DemoServiceB.demoMethodB的事务完成以后,
    他才继续执行。他与REQUIRED 的事务区别在于事务的回滚程度了。因为DemoServiceB.demoMethodB是新起一个事务,那么就是存在
    两个不同的事务。如果DemoServiceB.demoMethodB已经提交,那么DemoServiceA.demoMethodA失败回滚,DemoServiceB.demoMethodB是不会回滚的。如果DemoServiceB.demoMethodB失败回滚,
    如果他抛出的异常被DemoServiceA.demoMethodA捕获,DemoServiceA.demoMethodA事务仍然可能提交。
    5: NOT_SUPPORTED
    当前不支持事务。比如DemoServiceA.demoMethodA的事务级别是REQUIRED ,而DemoServiceB.demoMethodB的事务级别是NOT_SUPPORTED ,
    那么当执行到DemoServiceB.demoMethodB时,DemoServiceA.demoMethodA的事务挂起,而他以非事务的状态运行完,再继续DemoServiceA.demoMethodA的事务。
    6: NEVER
    不能在事务中运行。假设DemoServiceA.demoMethodA的事务级别是REQUIRED, 而DemoServiceB.demoMethodB的事务级别是NEVER ,
    那么DemoServiceB.demoMethodB就要抛出异常了。
    7: NESTED
    理解Nested的关键是savepoint。他与REQUIRES_NEW的区别是,REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
    而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
    例如:类serviceA和其中的方法methodA,类serviceB和其中的方法methodB,并且methodA的事务类型为
    REQUIRED,methodB的事务类型为NESTED.
    调用代码如下:
    代码一:
    ServiceA {
    /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { ServiceB.methodB(); } } ServiceB { /** * 事务属性配置为 PROPAGATION_REQUIRES_NEW */ void methodB() { } }

    改成:

    代码二:
    ServiceA {
    /** * 事务属性配置为 PROPAGATION_REQUIRED */ void methodA() { try { ServiceB.methodB(); } catch (SomeException) { // 执行其他业务, 如 ServiceC.methodC(); } } }

      这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )

      如果代码不做任何修改(代码一), 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException),也就是如果设置了rollbackfor,并且methodB抛出的异常符合rollbackfor设置的异常,那么methodA就会回滚,否者不会滚。

     
    
    
    
    
    

    学习链接

    jdbc事务和JAT事务管理

    spring事务和JDBC事务的关系

    事务的实现原理

  • 相关阅读:
    安卓adb
    图数据库学习
    分布式架构演进
    多活架构
    异地多活
    分布式CRDT
    技术架构的战略和战术原则
    分布式金融架构课笔记
    使用jackson进行json序列化时进行敏感字段脱敏(加密)或者忽略
    读书笔记《演进式架构》
  • 原文地址:https://www.cnblogs.com/htyj/p/10305780.html
Copyright © 2011-2022 走看看