事务以及Spring的事务管理
一、什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行
二、事务的特性(ACID)
- 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性: 执行事务前后,数据保持一致;
- 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
三、事务并发的问题
在实际的系统中,存在多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发操作可能会导致以下的问题:
- 脏读(Dirty read): 一个事务读到了另一个事务未提交的数据;
- 丢失修改(Lost to modify): 两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失;
- 不可重复读(Unrepeatableread): 在同一个事务T1中,进行多次查询,两次查询之间T2对数据进行了
修改
,导致T1两次查询的结果不一致; - 虚读/幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)
插入
了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
为了避免事务并发造成的问题,设置了不同级别的隔离防止以上问题的出现,下面是对隔离级别与并发问题的对应关系:
并发问题/隔离级别 | 脏读( Dirty Read ) | 丢失修改(Lost to modify) | 不可重复读( NonRepeatable Read ) | 虚读/幻读(Phantom read) |
---|---|---|---|---|
读未提交( Read uncommitted ) | 可能 | 可能 | 可能 | 可能 |
读已提交( Read committed ) | 不可能 | 可能 | 可能 | 可能 |
可重复读( Repeatable read ) | 不可能 | 不可能 | 可能 | |
可串行化( Serializable ) | 不可能 | 不可能 | 不可能 |
四、JDBC的事务管理
Connection提供了事务处理的方法,通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。除此之外,从JDBC 3.0中还引入了Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点。
五、Spring的事务管理
Spring中用于事务管理的三个核心接口分别是:
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus;
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为
。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。
Spring的事务管理有两种方式:
- 1、传统的
编程式事务管理
,即通过编写代码实现的事务管理; - 2、是基于 AOP 技术实现的
声明式事务管理
。
5.1、三大核心接口
PlatformTransactionManager
该接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下。
- TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
- void commit(TransactionStatus status):用于提交事务。
- void rollback(TransactionStatus status):用于回滚事务。
Spring并不直接管理事务,而是提供了多种事务管理器 ,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是:org.springframework.transaction.PlatformTransactionManager ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Spring为不同的持久层框架提供了不同的PlatformTransactionManager接口实现。
事务管理器 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 用于集成Hibernate框架时的事务管理,只支持Hibernate3+版本 |
org.springframework.orm.jpa.JpaTransactionManager | 用于集成JPA实现框架时的事务管理 |
... ... | ... ... |
TransactionDefinition
该接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下。
- String getName():获取事务对象名称。
- int getIsolationLevel():获取事务的隔离级别。
- int getPropagationBehavior():获取事务的传播行为。
- int getTimeout():获取事务的超时时间。
- boolean isReadOnly():获取事务是否只读。
TransactionStatus
该接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作:
名称 | 说明 |
---|---|
void flush() | 刷新事务 |
boolean hasSavepoint() | 获取是否存在保存点 |
boolean isCompleted() | 获取事务是否完成 |
boolean isNewTransaction() | 获取是否是新事务 |
boolean isRollbackOnly() | 获取是否回滚 |
void setRollbackOnly() | 设置事务回滚 |
5.2事务传播行为
事务传播行为的使用是为了解决业务层方法之间互相调用的事务问题
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
(重点)TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
(重点)TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
(重点)TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
5.3编程式事务管理
1、TransactionTemplate依赖DataSourceTransactionManager,
2、DataSourceTransactionManager依赖连接池DataSource构造
所以配置文件中如下:
<!--配置事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理的模板:Spring为了简化事务管理的代码而提供的类-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
3、在需要事务的业务层中注入TransactionTemplate类
4、编程实现事务操作
具体代码为:
/**
* @version V1.0
* @ClassName: AccoutServiceImpl
* @Description: 转账业务层实现类
* @author: Dreamice
* @date
**/
public class AccoutServiceImpl implements AccountService {
//注入转账的DAO类
private AccoutDao accoutDao;
public void setAccoutDao(AccoutDao accoutDao) {
this.accoutDao = accoutDao;
}
// 注入事务管理的模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(String out, String in, Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accoutDao.outMoney(out,money);
int i = 1/0;
accoutDao.inMoney(in,money);
}
});
}
}
5.4声明式事务管理
声明式事务管理方式一:基于TransactionProxyFactoryBean的方式
这种方式只能对一个业务类进行增强,所以企业开发中并不适合用
配置文件applicationContext.xml的配置:
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置业务层的代理-->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!--配置目标对象-->
<property name="target" ref="accountService"/>
<!--注入事务管理器-->
<property name="transactionManager" ref="transactionManager"/>
<!--注入事务属性-->
<property name="transactionAttributes">
<props>
<!--prop格式
* PROPAGATION
* ISOLATION
* readOnly
* -Exception
* +Exception :发生哪些异常实务不回滚
-->
<prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
声明式事务管理方式二:基于AspectJ的XML方式
经常使用,一旦配置好之后,类上不再需要添加任何东西
配置文件applicationContext.xml的配置:
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut1" expression="execution(* cn.dreamice.spring.demo3.AccountService+.*(..))"/>
<!--切面:将切入点与通知整合-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
声明式事务管理方式三:基于注解的方式
配置简单灵活,需要在业务层类或者方法上添加一个@Transaction的注解
配置文件applicationContext.xml的配置:
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--注册事务管理的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务层添加事务,可以在类或者方法上添加,如下:
@Transactional
public class AccoutServiceImpl implements AccountService {
//注入转账的DAO类
private AccoutDao accoutDao;
public void setAccoutDao(AccoutDao accoutDao) {
this.accoutDao = accoutDao;
}
//转账方法
@Transactional
public void transfer(String out, String in, Double money) {
accoutDao.outMoney(out,money);
int i = 1/0;
accoutDao.inMoney(in,money);
}
}
在使用 @Transactional 注解时,事务定义参数之间用“,”进行分隔。如下:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
参考资料
1、慕课网-Spring事务管理
2、掘金-可能是最漂亮的Spring事务管理详解
3、C语言中文网-Spring声明式事务管理
4、SF-Spring事务传播行为详解