【Spring应用级学习】事务(transaction)

事务

事务的四要素

ACID:atomicity、consistency、isolation、durability(持久性)

为什么要有事务

来自于 Wikipedia
Transactions in a database environment have two main purposes:

  1. To provide reliable units of work that allow correct recovery from failures and keep a database consistent even in cases of system failure, when execution stops (completely or partially) and many operations upon a database remain uncompleted, with unclear status.
  2. To provide isolation between programs accessing a database concurrently. If this isolation is not provided, the programs’ outcomes are possibly erroneous.

事务(Transaction)之所以存在于数据库中主要有两个目的:

  1. 一个事务可以作为一个比较安全的业务单元。在一个事务里如果遇到错误,是能够把数据恢复到曾经正确的状态的,从而使得在操作数据的时候即使出现系统错误(比如,一些操作在特殊情况下还没完成就被终止),也能保证数据库一致性。
  2. 多个程序并发的操作数据库的时候,事务提供了隔离性。如果没有事务隔离,那些程序的运行结果很可能是错误的。

事务隔离级别

锁是实现并发下安全操纵数据的一种方式,通过加锁和解锁,使的当前操作数据的用户有且仅有一个,保证了数据的安全性。

  1. 共享锁和排他锁

    • 共享锁:数据 A 被一个事务加上了共享锁,其他事务只能对 A 加共享锁,不能加排他锁
    • 排它锁:数据 A 被一个事务加上了排他锁,其他事务不能再对 A 加锁

    因此,在对同一数据进行操作的时候,共享锁可被多人持有,当共享锁全部解除,排它锁才能加锁;排它锁只能一人持有,当前人解除了排它锁,其他类型的锁(共享锁或排它锁)才能够加锁。

  2. 悲观锁和乐观锁

    • 悲观锁( Pessimistic Locking )
      悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

    • 乐观锁( Optimistic Locking )
      相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。

  3. 何谓数据版本?
    即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

事务隔离级别

有些业务“读”操作偏多,不需要每次事务都加锁限制。因此划分了事务隔离等级,通过分析自己业务 DDL,设定一个适合自己的级别,安全快速的操作数据。

  • 读未提交(Read Uncommitted):写数据时,其他人不能写(即在写数据操作未提交的情况下允许读数据,即有可能出现脏读),但能读;读数据时,其他人能读能写
  • 读已提交(Read Commit):写数据时,其他人不能写也不能读(即在提交写数据之前不允许其他操作,即有可能出现不可重复读);读数据时,其他人能读能写
  • 重复读取(Repeatable Read):写数据时,其他人不允许读写;读数据时,其他人不能写(因为读的时候其他人不能修改数据,因此可以重复读取,不会出现数据偏差)
  • 序列化(Serialiazable):通过序列化实现一个数据仅仅允许一个事务操作

事务的传播

为什么会有事务传播呢?当一个 Service 方法 A,调用另外一个 Service 方法 B,而且 A 已被一个事务 T1 管理起来,但是 B 方法也需要操作数据库,为了安全也要被事务管理,那是新创建一个事务还是沿用 T1 来管理 B 方法对数据库的操作呢?这时候就是事务需要传播的时候。

(以下来自于 JagoWang 的 GitHub

  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。也就是说业务方法需要在一个事务中运行,如果
    业务方法被调用时,调用业务方法的行为(方法)已经处在一个事务中,那么就加入到该事务,否则为自己创建一个新的事务。
    (默认传播属性)
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。也就是说如果业务方法在某个事务范围内被调用,则该方法成为该事务的一部分。如果业务方法在事务范围外被调用,则该方法在没有事务的环境下执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。也就是说业务方法只能在一个已经存在的事务中执行,
    业务方法不能发起自己的事务。如果业务方法在没有事务的环境下被调用,容器就会抛出例外。
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。也就是说业务方法被调用时,不管是否已经存在事务,业务方法总会为自己发起一个新的事务。如果调用业务方法的行为(方法)已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到业务方法执行结束,新事务才算结束,原先的事务才会恢复执行。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。也就是说业务方法不需要事务。如果方法没有被关联到一个事务中,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,
    原先的事务便会恢复执行。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。也就是说业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
  • PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按REQUIRED属性执行。
    它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对
    DataSourceTransactionManager事务管理器起效。

挂起(hang-up),方法 A 支持事务,方法 B 不支持事务,执行到方法 B 到时候可以将事务 A 挂起,执行完 B 方法以后,再将事务 A 恢复

事务的回滚

当事务发生错误的时候,需要返回到没错的状态,从而保持数据库数据的正确性,返回的过程叫做回滚。

JDBC 3.0 以后,回滚不能仅仅能够回滚到初始点,还可以可以根据保存点(SavePoint)回滚到指定的保存点上。

ThreadLocal

Spring 实现单例(Singleton)的一种方式,ThreadLocal 类中维护了一个 Map,key 为当前线程对象,value 为单例对象(比如一个 scope 设定为 singleton 的 Controller 对象),每个线程拥有一个自己的单例对象(其实已经不是单例了,应该有好多个 Controller,只不过被不同的线程持有,但是这样为同步互斥提供了一个新思路

Spring 事务处理的类

  • TransactionDefinition:用来定义事务的属性(事务隔离、事务传播、事务超时、只读状态)
  • TransactionStatus:当前事务的状态(当前savepoints、事务是否完成、间接的进行回滚操作等)
  • PlantformTransactionManager:用来对当前事务的管理(提交事务,回滚事务、获得事务)

事务的配置

可以通过 XML 和 Annotation 配置声明式事务

<!-- 配置事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <aop:config>
        <!-- 配置切入点,匹配到 service包中所有类的所有方法 -->
        <aop:pointcut id="all-service" expression="execution(* service.*.*(..))"/>
        <!-- 配置切面 -->
        <aop:advisor pointcut-ref="all-service" advice-ref="txAdvice"/>
    </aop:config>
    <!-- 配置增强 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="add*" rollback-for="Exception"/>
            <tx:method name="update*"/>
        </tx:attributes>
    </tx:advice>

参考《Spring 3.x 企业级应用开发实战》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值