什么是事务
- 事务就是一组操作数据库的动作集合。
- 动作集合被完整地执行,我们称该事务被提交。动作集合中的某一部分执行失败,整个动作集合提交失败,回到最初的状态,称为事务回滚。
- 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。
事务的特性
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。(操作)
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。(数据)
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。(数据)
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。(数据)
spring事务管理核心接口
Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。
事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是
org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。
1.JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
2.Hibernate事务
如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
基本事务属性的定义
上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面:
- 传播行为
- 隔离规则
- 回滚规则
- 事务超时
- 是否只读
TransactionDefinition接口内容如下:
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}
事务传播行为(重点)
什么是事务传播?
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。
spring事务传播行为有哪些?(重点掌握)
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,这是最常见的选择,也是默认的事务传播行为。 |
PROPAGATION_REQUIRED_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。
什么是事务挂起?
例如方法A支持事务,方法B不支持事务,方法A调用方法B。
在方法A开始运行时,系统为它建立Transaction,方法A中对于数据库的操作,会在该Transaction的控制之下。
这时,方法A调用方法B,方法A打开的Transation将挂起,方法B中任何数据库操作,都不在该Transaction的管理之下。
当方法B返回,方法A继续运行,之前的Transaction恢复,后面的数据库操作继续在该Transaction的控制之下提交或回滚。
测试案例
用户实现类中包含注册和注册送积分两个业务方法:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private BSService bsService;
//用户注册
@Override
public void registe(User user) {
userMapper.insert(user);
}
//用户注册并送积分
@Override
public void registeAndCredit(User user){
registe(user);
Credit credit = new Credit();
credit.setUsername(user.getName());
credit.setScore(20);
bsService.addCredit(credit);
}
}
业务实现类中有送积分的业务方法:
@Service
public class BSServiceImpl implements BSService {
@Autowired
private CreditMapper creditMapper;
//送积分
@Override
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
}
registeAndCredit方法中包含了用户注册和送积分两个数据库操作。
1.PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,这是最常见的选择,也是默认的事务传播行为。
场景一:registeAndCredit方法不添加事务,registe方法和addCredit方法均添加PROPAGATION_REQUIRED事务,addCredit方法抛出异常。
//送积分
@Override
@Transactional
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
//用户注册
@Override
@Transactional
public void registe(User user) {
userMapper.insert(user);
}
@Test
public void test03(){
User user = new User();
user.setName("小红");
user.setAge(26);
userService.registeAndCredit(user);
}
测试结果:用户注册成功,送积分失败。
测试分析:因为registeAndCredit没有添加事务,对于registe和addCredit来说,属于当前没有事务,所以各自新建事务,两个事务是独立的,互不影响。
场景二:registeAndCredit方法添加PROPAGATION_REQUIRED事务。对于registe和addCredit来说,属于存在事务,是否添加PROPAGATION_REQUIRED均属于同一个事务。
如果事务中异常被try catch处理后,事务正常提交。(嵌套调用方法除外)
使用此事务,在事务A中又开了一个事务B,其实AB是同一种事务,当事务回滚B时已经将事务标记为回滚,如果在A中try catch之后,事务A再次执行commit会报异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only(事务已经被标记为回滚)
代码如下:
//用户注册并送积分
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void registeAndCredit(User user){
registe(user);
Credit credit = new Credit();
credit.setUsername(user.getName());
credit.setScore(20);
try {
bsService.addCredit(credit);
}catch (Exception e){
System.out.println("%%%%%%%%%%%%%%%%%%%jiaixnxiao%%%%%%%%%%");
}
}
//送积分
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
执行registeAndCredit抛出事务已经被标记为回滚的异常。
2.PROPAGATION_REQUIRED_NEW
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。
场景一:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,并且抛出异常
- 如果不在registeAndCredit中对addCredit进行try catch的话,两个事务均回滚,因为addCredit抛出异常到registeAndCredit。(可以理解为addCredit开启新事物抛出异常并回滚,异常被当前事务registeAndCredit接收,因此也回滚)。
- registeAndCredit对addCredit进行try catch,新事物回滚,当前事务正常commit,所以用户注册成功,送积分操作回滚。
场景二:registeAndCredit添加PROPAGATION_REQUIRES_NEW事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,并且抛出异常,与场景一相同。
场景三:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,在registeAndCredit中抛出异常,因为addCredit创建了新事务,是两个不同的事务,所以用户注册回滚,送积分提交成功。
3.PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
场景一:与PROPAGATION_REQUIRED_NEW场景一相同。
场景三:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_NESTED事务,在registeAndCredit中抛出异常。addCredit创建了registeAndCredit事务的嵌套事务,外围事务抛出异常回滚,嵌套事务也回滚。所以用户注册回滚,送积分回滚。
新事务与嵌套事务
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:PROPAGATION_REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,而 PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。
注意问题
- 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。如将addCredit设置为PROPAGATION_MANDATORY,如果controller直接调用addCredit方法,将引发一个异常。正确的情况是:addCredit方法必须被另一个带事务的业务方法调用。所以 PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。
- 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设addCredit设置为PROPAGATION_NEVER,当registeAndCredit拥有一个事务时,addCredit方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的。
- 当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。