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