一、Spring事务的概述
事务的作用是什么?
简单来说,就是在数据层保障一系列的数据库操作同成功同失败(提交和回滚)
Spring事务作用︰在数据层或【业务层】保障一系列的数据库操作同成功同失败。
在业务层操作事务的好处/作用:
可以将业务层的方法里面包含的多个数据层事物操作放入到一个业务层的事务中管理,让它们同成功同失败。(具体可以通过后面的事务角色理解)
为何有了数据库事务还要使用Spring事务?
引用文章:为何有了数据库事务还要使用Spring事务`
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
//1、获取连接
Connection con = DriverManager.getConnection()
//2、开启事务
con.setAutoCommit(true/false);
//3、执行CRUD
//4、提交事务/回滚事务
con.commit() // con.rollback();
//5、关闭连接
conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。 那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?
下面简单地介绍下,注解方式为例子
1、配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
2、Spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
3、真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
二、快速入门(注解)
案例:模拟银行账户间转账业务
需求:实现任意两个账户间转账操作
需求微缩︰A账户减钱,B账户加钱(同步执行,如果报错则都不能执行)
步骤1、SpringConfig配置类 开启注解式事务驱动
@EnableTransactionManagement//开启注解式事务驱动
步骤2、在业务层的接口方法上添加Spring事务管理
@Transactional的作用:配置当前接口方法具有事务(业务层接口或这实现类)
说明:
- 添加位置——接口实现类或接口实现方法上,而不是接口类中
- @Transactional 注解应该只有被应用到 public 方法上才起作用,这是由 Spring AOP 的本质决定的。
- @Transactional实质是使用了 JDBC 的事务来进行事务控制的
实现原理:
@Transactional :基于 Spring 的动态代理的机制
1 、事务开始时,通过AOP机制,生成一个代理connection对象,
并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。
在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,
执行所有数据库命令。
[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
(物理连接 connection 逻辑上新建一个会话session
DataSource 与 TransactionManager 配置相同的数据源
2、 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,
然后关闭该代理 connection 对象。
(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)
引用文章:@Transactional 详解
步骤3、设置事务管理器(同时交给Spring管理)
作用:管理事务(如设置数据源)
设置事务管理器的的3点说明(对具体内容感兴趣可以自行观看下面的文章)
1、事务管理器(接口)
PlatformTransactionManager接口——真正管理事务的对象
2、创建对象(实现事务管理器接口)
创建的是 接口的实现类DataSourceTransactionManager数据源状态管理对象
3、该对象的作用:可以通过该对象来管理数据源。如,设置数据源
附:设置的数据源对象是自己注册到Spring容器的数据源对象
Spring进行事务管理的常用API
1)PlatformTransactionManager:平台事务管理器
Spring进行事务操作时候,主要使用一个PlatformTransactionManager接口,它表示事务管理器,即真正管理事务的对象。
Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring针对不同的持久化框架,提供了不同PlatformTransactionManager接口的实现类:
org.springframework.jdbc.datasource.DataSourceTransactionManager :使用 Spring JDBC或iBatis 进行持久化数据时使用
org.springframework.orm.hibernate3.HibernateTransactionManager :使用 Hibernate版本进行持久化数据时使用
2)Spring的这组接口是如何进行事务管理的
平台事务管理器根据事务定义的信息进行事务的管理,事务管理的过程中产生一些状态,将这些状态记录到TrancactionStatus里面。
3)TransactionStatus:事务的状态
在上面 PlatformTransactionManager 接口有一个方法getTransaction(),这个方法返回的是 TransactionStatus对象,然后程序根据返回的对象来获取事务状态,然后进行相应的操作。
而 TransactionStatus 这个接口的内容如下:
这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
引用文章:Spring事务控制–Spring中的事务处理
步骤4、核心业务
①∶数据层提供基础操作,指定账户减钱(outMoney ),指定账户加钱(inMoney)
②: 业务层提供转账操作( transfer ),调用减钱与加钱的操作
③∶提供2个账号和操作金额执行转账操作
步骤5、开始测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() throws IOException {
accountService.transfer("Tom","Jerry",100D);
}
}
三、Spring事务角色
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
Spring事务在业务层同成功同失败的原理:
四、@Transaction事务属性
使用格式如下
public interface AccountService {
//配置当前接口方法具有事务
@Transactional(readOnly = true,timeout = -1)//设置只读,永不超时
public void transfer(String out,String in ,Double money) ;
}
4.1 设置事务回滚异常(属性rollbackFor):
程序中只有两种类型 会自动回滚
- Error类型
- RuntimeException运行时异常 (如NullPointerException、IndexOutOfBoundsException等)这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
不会自动回滚的类型
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。
如果非运行时异常在多个事务中发生,会造成数据库的数据出现问题,比如
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);//dao事务1,(执行了)
// if(true){throw new IOEXception();};//模拟出现异常情况
accountDao.inMoney(in,money);//dao事务2(没有执行)
}
}
那么,应该如何避免这种问题呢?通过rollbackFor属性
public interface AccountService {
//配置当前接口方法具有事务
@Transactional(rollbackFor = IOException.class)//设置事务回滚异常(class)
public void transfer(String out,String in ,Double money) ;
}
4.2 事务传播行为(属性propagation):
事务传播行为:事务协调员对事务管理员所携带事务的处理态度
附:propagation:传播
场景:需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志
1、转账功能(同上):减钱和加钱事务 同成功同失败
2、无论转账操作是否成功,记录日志功能一定要执行(try…finally)
上方代码会失败:虽然logService.log方法一定会执行,但是,如果执行中发生了异常,会全部回滚(记录日志功能也被回滚了),因为LogService 【默认】加入了事务T(同成功,同失败)
那么,LogService接口如何自己单独开启事务呢(如下图)?
使用propagation
public interface LogService {
//propagation设置事务属性:传播行为设置为当前操作需要新事务
//REQUIRES_NEW:不管有没有事务,都自己单独新建一个事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
void log(String out, String in, Double money);
}
事务传播行为:事务协调员对事务管理员所携带事务的处理态度
其他的传播属性如下:
附:dao层、serviceImpl层、service层分别负责什么?
Dao接口:关于数据库持久化相关操作
serviceImpl层:对多个数据库数据进行组合,形成相关的功能——事务的组合
service层:对serviceImpl层的事务进行管理
如:
1、处理事务回滚异常
2、设置处理事务传播行为(加入事务、不加入事务、自己新建事务)
专栏文章:
Spring框架(一):概述及简单使用(基于XML方式)