一、事务简介
1、事务就是一系列的动作, 它们被当做一个单独的工作单元
2、事务的四个关键属性(ACID)
- 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
- 一致性(consistency): 一旦所有事务动作完成, 事务就被提交。 数据和资源就处于一种满足业务规则的一致性状态中。
- 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏。
- 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响。 通常情况下, 事务的结果被写到持久化存储器中。
3、Spring 既支持编程式事务管理, 也支持声明式的事务管理
- 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码。
- 声明式事务管理: 大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务管理。
二、声明式事务
1、Spring配置
配置文件引入tx命名空间
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager" />
2、service层添加注解
三、事务的传播行为
1、简介
当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播。
例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行
2、实例
传播属性配置在被调用的方法上,从而传播到调用者身上。
/**
* 使用 propagation 指定事务的传播行为<br>
* 默认取值为 REQUIRED, 即当前的事务方法被另外一个事务方法调用时,使用调用方法的事务<br>
* REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起.
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void purchase(String username, String isbn) {
// 1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 2. 更新数的库存
bookShopDao.updateBookStock(isbn);
// 3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
另一个事务进行调用上方的事务
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for (String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
3、解析
(1)REQUIRED
在 checkout() 方法的开始和终止边界内只有一个事务。 这个事务只在 checkout() 方法结束的时候被提交
*Tx1指purchase() 方法的事务取消,使用checkout()的事务
(2)REQUIRES_NEW
方法必须启动一个新事务, 并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
*每个purchase()的事务都使用,checkout()的事务也使用,当purchase()事务开始时checkout()挂起,但从头执行到尾部。
四、事务的隔离级别
1、并发事务所导致的问题
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题
- 脏读: 对于两个事物 T1,T2, T1 读取了已经被 T2 更新但 还没有被提交的字段。之后,若 T2 回滚,T1读取的内容就是临时且无效的。
- 不可重复读:对于两个事物 T1, T2, T1 读取了一个字段,然后 T2 更新了该字段。之后,T1再次读取同一个字段,值就不同了。
- 幻读:对于两个事物 T1, T2, T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。之后,如果 T1 再次读取同一个表,就会多出几行。
从理论上来说, 事务应该彼此完全隔离,以避免并发事务所导致的问题。然而,那样会对性能产生极大的影响,因为事务必须按顺序运行。在实际开发中,为了提升性能,事务会以较低的隔离级别运行。
2、隔离级别
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
Mysql 支持 4 中事务隔离级别
3、实例
/**
* 使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
*/
@Transactional(isolation = Isolation.READ_COMMITTED)
@Override
public void purchase(String username, String isbn) {
// 1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 2. 更新数的库存
bookShopDao.updateBookStock(isbn);
// 3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
五、回滚、超时和只读
1、回滚
默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类
- rollbackFor: 遇到时必须进行回滚
- noRollbackFor: 一组异常类,遇到时必须不回滚
2、超时
超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源
使用 timeout 指定强制回滚之前事务可以占用的时间,超时属性以秒为单位来计算
实例:@Transactional(timeout = 3)
3、只读
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务
使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据
实例:@Transactional(readOnly = true)
六、使用xml配置事务
1、配置需要的Bean
<!-- 配置 bean -->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
2、配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
3、配置事务属性
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
4、配置事务切入点
以及把事务切入点和事务属性关联起来
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>