zoukankan      html  css  js  c++  java
  • Spring事务的一些基本知识(一)

    【事务的属性和行为】

    事务的ACID属性:
    原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
    隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
    持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
    一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

    对一致性的理解:
    其它三种都好理解,但是一致性的含义似乎不那么明确。我们可以看看Wikipedia中关于数据库事务一致性的定义:
    Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades,triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct.
    这里的一致性是说,事务保证数据库里维护的数据总是满足约束的,不管这些数据怎么转换状态,其中的数据总是与约束保持一致的。举个例子,我们做事情,总是满足各种规章制度,那么也可以说我们的行为与规章制度是有一致性的;与其它工程师联合开发功能时,我们约定的协议,接口总是遵守的,那么也可以说与协议有一致性。而这里,事务保证了写入数据的数据,总是与这些约束保持一致的,这些数据即便是状态转变了,也是从一种满足约束的状态变为另一种满足约束的状态,不会打破这种规则。

    【事务并发的问题和隔离级别】
    -------事务并发的问题-------
    脏读:事务A读到了另一个未提交事务B修改过的数据。如果事务B回滚了刚才修改的记录,那么A读的数据就是不存在的;
    --读到了修改但未提交事务的数据。

    不可重复读:事务A在开启之后,不断地去读某一行数据。此时,事务B去修改了那条数据并提交,而没有提交的事务A去查询到的数据就变化了。这种现象就是不可重复读。
    --事务未提交,但读到的数据发生了改变。

    幻读:事务A在开启后,不断去读某个条件范围的数据。此时事务B去insert一条符合范围的数据,而没有提交的事务A查询到的数据量发生了变化。
    --事务未提交,但读到的数据范围发生了改变。

    不可重复读和幻读看起来都是未提交的事务在多次使用相同查询语句后查询到的数据发生了改变,但由于隔离的级别不同才会导致这两种情况,所以将二者区分开来。不可重复读针对的主要是UPDATE,DELETE的情况,而幻读针对的是INSERT的情况。

    -------事务的隔离级别-------
    一共有四个:读未提交,读已提交,可重复读以及可串行化。
    隔离级别/对性能的影响的比较:可串行化>可重复读>读已提交>读未提交,考虑到性能,一般建议MySQL的隔离级别设置为可重复读,这也是InnoDB默认的隔离级别。

    读未提交,即可以读到其它事务开启后、未提交前修改的数据。可发生脏读、不可重复读与幻读,一般很少使用此隔离级别。除非整个表只是用来读取数据,完全没有写数据的情况。
    读已提交,即只能读到其它事务已经提交的数据,可以解决脏读,但无法解决不可重复读和幻读的问题。一般也很少使用此隔离级别。
    可重复读,事务B只能在事务A修改过数据并提交,且自己也提交事务之后,才能读取到事务A修改之后的数据。它解决了脏读和不可重复读的问题,但可能发生幻读问题。
    可串行化,读读不阻塞,读写、写读、写写都阻塞,此时可以保证不脏堵、可重复读、不幻读。

    【事务的传播机制】
    Spring支持7种事务传播行为,通俗说就是具有事务控制的@Service之间相互调用时的一些规则和特点,在理解了Spring对标注有@Transactional的public方法标记生成动态代理类,并且由外部调用@Transactional方法的时候才会使用该动态代理类进行事务控制时,以下的几种传播行为就很好理解了,同时也会明白哪种情况下这个注解会失效。

    对几种传播机制的说明:
    PROPAGATION_REQUIRED(Default)
    说明:表示当前方法必须在一个具有事务的上下文中运行,如果调用该方法的方法是具有事务的,那么这方法在该事务中运行;如果调用该方法的方法是没有事务的,那么重新开启一个事务
    PROPAGATION_SUPPORT
    说明:表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
    PROPAGATION_MANDATORY
    说明:表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常
    PROPAGATION_REQUIRES_NEW
    说明:表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。
    PROPAGATION_NOT_SUPPORTED
    说明:表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行
    PROPAGATION_NEVER
    说明:表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常
    PROPAGATION_NESTED
    说明:表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则新开启一个事务。

    这几种机制的灵活应用可以让我们对于业务场景的管理更为精细,但需要正确使用也是有一定的套路的,这里有一篇参考,讨论了一些错误使用事务传播机制的危害,可以借鉴:
    https://cache.one/read/9666662
    而下面这篇参考给出了一个在常规CRUD场景中比较实用的文章,看完对于事务传播机制的理解会有帮助:
    https://blog.51cto.com/u_15127649/2782751

    【AOP增强】
    aop的一些概念,推荐大家看看:
    https://blog.csdn.net/qukaiwei/article/details/50367761

    动态代理是Spring实现AOP的默认方式,分为两种:JDK动态代理和CGLIB动态代理。JDK动态代理面向接口,通过反射生成目标代理接口的匿名实现类;CGLIB动态代理则通过继承,使用字节码增强技术(或者objenesis类库)为目标代理类生成代理子类。Spring默认对接口实现使用JDK动态代理,对具体类使用CGLIB,同时也支持配置全局使用CGLIB来生成代理对象。

    我们在切面配置中会使用到@Aspect注解,这里用到了Aspectj的切面表达式。Aspectj是java语言实现的一个AOP框架,使用静态代理模式,拥有完善的AOP功能,与Spring AOP互为补充。Spring采用了Aspectj强大的切面表达式定义方式,但是默认情况下仍然使用动态代理方式,并未使用Aspectj的编译器和织入器,当然也支持配置使用Aspectj静态代理替代动态代理方式。Aspectj功能更强大,比方说它支持对字段、POJO类进行增强,与之相对,Spring只支持对Bean方法级别进行增强。

    Spring对方法的增强有五种方式:
    前置增强(org.springframework.aop.BeforeAdvice):在目标方法执行之前进行增强;
    后置增强(org.springframework.aop.AfterReturningAdvice):在目标方法执行之后进行增强;
    环绕增强(org.aopalliance.intercept.MethodInterceptor):在目标方法执行前后都执行增强;
    异常抛出增强(org.springframework.aop.ThrowsAdvice):在目标方法抛出异常后执行增强;
    引介增强(org.springframework.aop.IntroductionInterceptor):为目标类添加新的方法和属性。

    【Spring事务抽象】
    声明式事务的实现就是通过环绕增强的方式,在目标方法执行之前开启事务,在目标方法执行之后提交或者回滚事务。
    统一一致的事务抽象是Spring框架的一大优势,无论是全局事务还是本地事务,JTA、JDBC、Hibernate还是JPA,Spring都使用统一的编程模型,使得应用程序可以很容易地在全局事务与本地事务,或者不同的事务框架之间进行切换。
    下图是Spring事务抽象的核心类图:

    接口PlatformTransactionManager定义了事务操作的行为,其依赖TransactionDefinition和TransactionStatus接口:
    PlatformTransactionManager:事务管理器
    getTransaction方法:事务获取操作,根据事务属性定义,获取当前事务或者创建新事物;
    commit方法:事务提交操作,注意这里所说的提交并非直接提交事务,而是根据当前事务状态执行提交或者回滚操作;
    rollback方法:事务回滚操作,同样,也并非一定直接回滚事务,也有可能只是标记事务为只读,等待其他调用方执行回滚。
    TransactionDefinition:事务属性定义
    getPropagationBehavior方法:返回事务的传播属性,默认是PROPAGATION_REQUIRED;
    getIsolationLevel方法:返回事务隔离级别,事务隔离级别只有在创建新事务时才有效,也就是说只对应传播属性PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW;
    getTimeout方法:返回事务超时时间,以秒为单位,同样只有在创建新事务时才有效;
    isReadOnly方法:是否优化为只读事务,支持这项属性的事务管理器会将事务标记为只读,只读事务不允许有写操作,不支持只读属性的事务管理器需要忽略这项设置,这一点跟其他事务属性定义不同,针对其他不支持的属性设置,事务管理器应该抛出异常。
    getName方法:返回事务名称,声明式事务中默认值为“类的完全限定名.方法名”。
    TransactionStatus:当前事务状态
    isNewTransaction方法:当前方法是否创建了新事务(区别于使用现有事务以及没有事务);
    hasSavepoint方法:在嵌套事务场景中,判断当前事务是否包含保存点;
    setRollbackOnly和isRollbackOnly方法:只读属性设置(主要用于标记事务,等待回滚)和查询;
    flush方法:刷新底层会话中的修改到数据库,一般用于刷新如Hibernate/JPA的会话,是否生效由具体事务资源实现决定;
    isCompleted方法:判断当前事务是否已完成(已提交或者已回滚)。

    【参考】

    https://zhuanlan.zhihu.com/p/54067384
    https://www.zhihu.com/question/31346392?sort=created
    https://cache.one/read/9666662
    https://blog.51cto.com/u_15127649/2782751

  • 相关阅读:
    this直接加在函数或者是 “原型”对象的区别
    网易云课堂_C语言程序设计进阶_第一周:数据类型:整数类型、浮点类型、枚举类型_1计算分数精确值
    #include <stdbool.h>
    网易云课堂_C语言程序设计进阶_第一周:数据类型:整数类型、浮点类型、枚举类型
    #include <locale.h> #include <locale>
    C语言复习
    Apache HTTP Server
    深拷贝_浅拷贝
    04737_C++程序设计_第4章_类和对象
    经典算法_链表
  • 原文地址:https://www.cnblogs.com/bruceChan0018/p/15737758.html
Copyright © 2011-2022 走看看