Spring中事务管理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中事务管理相关的知识,希望对你有一定的参考价值。

1.什么是事务?

事务是逻辑上的一组操作,这组操作要么全部成功,要么全部失败

2.事务具有四大特性ACID

    1)原子性(Atomicity):即不可分割性,事务要么全部被执行,要么就全部不被执行。如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生转换;如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态会发生状态转换

    2)一致性(Consistency)事务的执行使得数据库从一种正确状态转换成另一种正确状态例如对于银行转账事务,不管事务成功还是失败,应该保证事务结束后两个转账账户的存款总额是与转账前一致的。

    3)隔离性(Isolation)并发执行的事务隔离的,一个不影响一个,每个事务都有各自的完整数据空间。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。通过设置数据库的 隔离级别 ,可以达到不同的隔离效果。

    4)持久性(Durability):当提交了事务之对数据库中的数据进行的操作持久生效,不会无缘无故的回滚.

 

 

 【事务并发】,如果没有采取必要的隔离机制,会引起那些并发问题?

↑ 多个事务同时运行                (丢失更新、脏读、不可重复读、幻读

 

假设张三办了一张 银行卡,余额100元

      

第一类丢失更新:两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。

脏读:一个事务读取另外一个事务还没有提交的数据

不可重复读(虚读):针对修改   一个事务对同一行数据重复读取两次,但是却得到了不同的结果。

第二类丢失更新一个事务覆盖另一个事务已提交的数据   一个事务的更新覆盖了另一个事务的更新。更新丢失本质上是写操作的冲突,解决办法是一个一个地写。

 

幻读:针对增加删除  一个事务两次读取一个范围的记录,两次读取的记录数不一致

幻象读本质上是读写操作的冲突,解决办法是读完再写。

 

 

不可重复读和幻读的区别:

 

    1)不可重复读 的重点是修改:

    同样的条件, 你读取过的数据, 再次读取出来发现值不一样了

 

    2)幻读 的重点在于新增或者删除

    同样的条件, 第1次和第2次读出来的记录数不一样

 

解决并发问题的途径是什么?答案是:采取有效的隔离机制。怎样实现事务的隔离呢?隔离机制的实现必须使用锁

 

当数据库系统采用Read Committed隔离级别时(优先考虑,它能够避免脏读,而且具有较好的并发性能。,会导致不可重复读第二类丢失更新的并发问题。可以在应用程序中采用悲观锁乐观锁来避免这类问题:

 

数据库系统提供了四种事务隔离级别供用户选择:

①Read Uncommitted (读取未提交)防止第一类丢失更新,对应整数1

②Read Committed(读取已提交)防止脏读,对应整数2

③Repeatable Read(可重复读)防止不可重复读及第二类丢失更新,不可以预防幻读,对应整数4

④Serializable(串行话)隔离级别最高,但是并发性能最差,对应整数8

 

隔离级别越来越严格,数据安全和真实性越来越高,但并发性能越来越低。

 

 

1)乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁是在更新会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。

Read Committed + 乐观锁 -> Repeatable Read

 

①在实体类中加入存储版本的字段version,类型可以为short/int/long,并添加setter/getter方法

 

private int version;(在set 方法里面 更改修饰符 为private)

 

②在映射文件中紧跟id标签,加入<version>标签

 

<version name="version" column="version"></version>

 

效果: 更改一次 version 由0变1  不更改 提交 无效果

 

 

2)悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会block直到它拿到锁。为了避免当前事务的操作受到干扰,先锁定资源。尽管悲观锁能够防止丢失更新和不可重复读这类并发问题,但是它影响并发性能,因此应该很谨慎使用悲观锁。

 

 

使用方法:

Dept dept = (Dept)session.load(Dept.class, 1l, LockOptions.UPGRADE);

 

利用数据库中的独占锁来完成:例如:select * from employee for update;

在Hibernate中,使用悲观锁来控制并发,其本质就是利用数据库中的独占锁

 

 

(两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。)

 

 

3.Spring中事务的管理

Spring中主要提供了以下3个接口来对事务进行管理

PlatformTransactionManager 平台事务管理器

这个接口下包含以下实现类

DataSourceTransactionManager、HibernateTransactionManager、JdoTransactionManager、JpaTransactionManager等

其中常用

I:DataSourceTransactionManager 用来管理jdbc事务以及MyBatis/iBatis事务

II:HibernateTransactionManager 用来管理Hibernate事务

 

TransactionDefinition 事务定义:定义一些事务的信息(包括事务的隔离级别、传播行为、超时、只读)

隔离级别:针对并发选择合适的隔离级别

ISOLATION_DEFAULT:使用数据库默认隔离级别,mysql默认为REPEATABLE_READ,oracle默认为READ_COMMITTED

传播行为

***PROPAGATION_REQUIRED:支持当前事务,如果没有,就新建一个

PROPAGATION_SUPPORTS:支持当前事务,如果没有,就不使用事务

PROPAGATION_MANDATORY:支持当前事务,如果没有,抛出异常

----------------------

**PROPAGATION_REQUIRES_NEW:如果有事务存在,挂起当前事务,创建一个新的事务

PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果有事务,挂起当前事务

PROPAGATION_NEVER:以非事务方式运行,如果有事务,抛出异常

-----------------------

*PROPAGATION_NESTED:嵌套事务,例如a方法调用b方法,可以在某个位置设置一个保存点,如果b中出现异常需要回滚事务,可以选择回滚到保存点或者初始位置

其它

TIMEOUT_DEFAULT:设置超时时间

 

TransactionStatus 事务的具体运行状态

hasSavepoint():判断是否有保存点

isCompleted():判断是否完成

isNewTransaction():判断是新事务

flush():将缓存(seesion)中的数据保存数据库中

 

Spring有两种事务管理方式:①编程式  ②声明式。

编程式的比较灵活,但是代码量大,存在重复的代码比较多;

而声明式事务管理比编程式更灵活方便。

 

编程式事务管理(通过模版transactionTemplate):

需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法<bean     id="transactionTemplate"

class="org.springframework.transaction.support.TransactionTemplate">

       <property name="transactionManager" ref="transactionManager"></property>

</bean>

(引入配置好的事务管理器        transactionManager

I:DataSourceTransactionManager II:HibernateTransactionManager 注入dataSource)

 

声明式事务管理:建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

①通过TransactionProxyFactoryBean类生成代理来控制事务

I:配置事务管理器

II:配置TransactionProxyFactoryBean

<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<!-- 配置目标bean,即为哪个bean生成代理 -->

<property name="target" ref="accountService"></property> 

<!-- 配置事务管理器 -->

<property name="transactionManager" ref="transactionManager"></property> 

<!-- 主要设置为哪些方法添加事务,以及事务的传播方式、隔离级别等 -->

<property name="transactionAttributes">    

        <props>

    <prop key="transfer">+java.lang.ArithmeticException,PROPAGATION_REQUIRED</prop>

        </props>

    </property>

</bean>

+表示即使抛出该异常事务同样要提交(异常后面的不提交)

- 表示抛出该异常时需要回滚

 

 传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]

 

 

②使用aop切面管理事务

 I:配置事务管理器

II:配置aop

<aop:config>

       <aop:pointcut expression="execution(* com.sw.spring.transaction.demo3.*Service.*(..))" id="apointcut"/>

       <aop:advisor advice-ref="txAdvice" pointcut-ref="apointcut"/>

    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">

       <tx:attributes>

           <tx:method name="transfer" propagation="REQUIRED"/>

       </tx:attributes>

    </tx:advice>

 

基于注解的声明式事务管理(主要用这个)      @Transactional

I:配置事务管理器

II:开启注解事务

<tx:annotation-driven transaction-manager="transactionManager"/>

III:在Service上加上注解:@Transactional

以上是关于Spring中事务管理的主要内容,如果未能解决你的问题,请参考以下文章

如何管理在每个 git 版本中添加私有代码片段?

使用 Git 来管理 Xcode 中的代码片段

Spring Rest 文档。片段生成时 UTF-8 中间字节无效 [重复]

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

片段事务中的实例化错误