1.1Spring事务简介
- 事务作用:在数据层保障一系列的数据库操作同成功或同失败
- Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功或同失败
public interface PlatformTransactionManager{
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionSattus status) throws TransactionException;
}
public class DataSourceTransactionManager{
...
}
1.2案例:银行账户转账
模拟银行账户间转账业务
需求:实现任意两个账户间转账操作
需求微缩:A账户减钱,B账户加钱
分析:
- 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
- 业务层提供转账操作(transfer),调用减钱和加钱的操作
- 提供2个账号和操作金额执行转账操作
- 基于Spring整合MyBatis环境搭建上述操作
结果分析:
- 程序正常执行时,账号金额A减B加,没有问题
- 程序出现异常,转账失败,单是异常之前操作成功,异常之后操作失败,整体业务失败
案例实现步骤:
①在业务层接口上添加Spring事务管理
public interface AccountService{ @Transactional public void transfer(String out,String in,Double money); }
说明:Spring注解式事务通常添加在业务层接口中,相比于添加到业务层的实现类中,Spring在这一点上也做到了降低耦合
注解式业务可以添加到业务方法上表示开启事务,也可以添加到接口上表示当前接口所有方法开启事务
②设置事务管理器
@Bean public PlatformTransactionManager transactionManager(DataSource dataSoure){ DataSouceTransactionManager ptm = new DataSourceTransactionManager(); ptm.setDataSource(dataSource); return ptm; }
注意:事务管理器要根据实现技术进行选择,Mybatis框架使用的是JDBC事务
③开启注解式事务驱动
@Configuration @ComponentScan("com.hcx") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig{ }
1.3事务角色
- 事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常带着数据层方法,也可以是业务层方法
未开启Spring事务之前的工作流程
数据层中的AccountDao中的outMoney、inMoney是修改操作,会各自开启一个事务T1、T2
业务层中的AccountService的transfer没有事务
运行过程中如果没有抛出异常,则T1和T2都正常提交,数据正确
若在方法1之后和方法2之前出现异常,事务T1会执行成功提交事务,事务T2因为抛异常不会被执行就会导致数据出现错误
开启Spring事务之后的工作流程
transfer上添加了@Transactional注解,则该方法上就会添加一个事务T
outMoney()的事务T1\inMoney()的事务T2均加入到transfer的事务T中
使他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,从而保证数据的准确性。
注:Mybstis配配置类中SqlSessionFactoryBean和JDBC配置类中的事务管理器DataSourceTransactionManager中是同一个dataSource,因此才可以做到统一管理,即同通过相同数据源进行管理
事务相关配置
readOnly:设置只读事务。true只读事务,false读写事务,增删改要设为false,查询设为true,默认设置为false即读写事务。
timeout:设置事务超时时间。单位秒,在timeout之内事务没有提交成功就自动回滚,-1表示永不超时。
rollbackFor:当出现指定异常进行事务回滚
public interface AccountService { /** * 转账操作 * @param out 传出方 * @param in 转入方 * @param money 金额 */ //配置当前接口方法具有事务 public void transfer(String out,String in ,Double money) throws IOException; } @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Transactional public void transfer(String out,String in ,Double money) throws IOException{ accountDao.outMoney(out,money); //int i = 1/0; //这里异常事务会回滚 if(true){ throw new IOException(); //这里的IO异常事务默认不会参与回滚 } accountDao.inMoney(in,money); } }
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; //设置IOException回滚异常 @Transactional(rollbackFor = {IOException.class}) public void transfer(String out,String in ,Double money) throws IOException{ accountDao.outMoney(out,money); //int i = 1/0; if(true){ throw new IOException(); //加上roollbackFor之后异常事务回滚 } accountDao.inMoney(in,money); } }
注:使用rollbackFor属性来设置出现IOException异常回滚
noRollbackFor:当出现指定异常不进行事务回滚
1.4案例:转账业务追加日志
需求分析:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志
分析:
基于转账操作案例添加日志模块,实现数据库中记录日志
业务层转账操作(transfer),调用减钱,加钱与记录日志功能
实现效果预期:
无论转账是否成功,均进行转账操作的日志留痕
存在问题:
日志的记录与转账操作隶属于同一事务,同成功同失败
失效效果预取改进:
无论转账操作是否成功,日志必须保留,需要设置事务传播行为propagation
事务传播行为------propagation属性:事务协调员对事务管理员所携带事务的处理态度
日志记录失败原因分析: log方法、inMoney方法和outMoney方法都属于增删改各自开启事务T1,T2,T3且都属于事务协调员,transfer加了@Transactional注解,也开启了事务T,且属于事务管理员,事务管理员T会把事务协调员T1,T2,T3都加入到事务T中
这样一来,当转账失败时,事务T一旦发生回滚,则所有的事务都会回滚,造成日志没有记录
改进实现修改logService改变事务的传播行为
propagation属性为Propagation.REQUIRES_NEW
REQUIRES_NEW实现新建一个事务
@Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; //propagation设置事务属性:传播行为设置为当前操作建立新事物,不和其他人一起玩 @Transactional(propagation = Propagation.REQUIRES_NEW) public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } }
事务传播行为总结:
说明:
PROPAGATION_REQUIRED 如果事务管理员没有事务,就新建一个事务,如果事务管理员已经存在一个事务中,加入到这个事务中。
PROPAGATION_SUPPORTS 支持当前事务,如果当前事务管理员没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前事务管理员存在事务,则事务协调员新建一个事务;否则,仍然新建一个事务,无论如何就是自己玩。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前事务管理员存在事务,事务协调员不加入,如果当前事务管理员未开启事务,同样不开启事务。
PROPAGATION_NEVER 以非事务方式执行,如果当前事务管理员存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前事务管理员存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。