一、定义
事务管理是程序开发中必不可少的技术,它用来确保数据的完整性和一致性。事务就是一系列方法组合执行,其中每一个方法分开执行都是一个单独的工作单元,但如果使用事务,这些方法的组合,要么全部执行完成,要么全部不执行。
二、特性
事务的四个关键属性(ACID)
原子性(atomicity):事务是一个原子操作,由一系列方法组成,事务的原子性确保方法要么全部完成,要么完全不起作用;
一致性(consistency):一旦所有事务方法完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中;
隔离性(isolation):可能有许多事务,会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏;
持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,通常情况下,事务的结果被写到持久化存储器中。
三、声明式事务(注解方式)
1、在spring配置文件中加入事务的配置(注意要管理的数据源)
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2、启用事务注解扫描 (注意配置事务管理器)
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
3、在方法上使用注解@Transactional
@Transactiona
@Override
public void purchase(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
四、事务的传播行为
1、当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
2、事务的传播行为,可以由传播属性propagation指定。
3、Spring定义了7种类传播行为,如下图:
4、使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
(1)默认取值为REQUIRED,即使用调用方法的事务;
(2)REQUIRES_NEW:采用自己的事务, 调用的事务方法的事务被挂起;
(3)实例代码:
@Transactional(propagation=Propagation.REQUIRES_NEW)
5、示例代码:
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
//结账事务
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn: isbns){
bookShopService.purchase(username, isbn);
}
}
}
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
//购买事务
@Transactional
@Override
public void purchase(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行,这个默认的传播行为就是REQUIRED,因此在checkout()方法的开始和终止边界内只有一个事务,这个事务只在 checkout()方法结束的时候被提交。
另一种常见的传播行为是 REQUIRES_NEW,它表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起之前的事务。
五、事务的隔离级别
1、并发事务所导致的问题:当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时, 可能会出现许多意外的问题。
2、并发事务所导致的问题,可以分为下面三种类型:
(1)脏读:对于两个事务T1、T2,当T1读取了已经被T2更新,但还没有被提交的字段之后, 若T2回滚,T1读取的内容就是临时且无效的。
(2)不可重复读:对于两个事务T1、T2,当T1读取了一个字段, 然后T2更新了该字段之后, T1再次读取同一个字段,值就不同。
(3)幻读:对于两个事务T1、T2, 当T1从一个表中读取了一个字段, 然后T2在该表中插入了一些新的行之后, 如果T1再次读取同一个表, 就会多出几行。
3、从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而,那样会对性能产生极大的影响,因为事务必须按顺序运行。
4、在实际开发中,为了提升性能,事务会以较低的隔离级别运行。
5、事务的隔离级别可以通过隔离事务属性isolation指定,如下:
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED)
6、isolation的取值如下:
其中,最常用的取值为READ_COMMITTED
7、Transactional中属性值补充
(1)默认情况下Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置,通常情况下去默认值即可;
(2)使用readOnly指定事务是否为只读,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务,若真的是一个只读取数据库值的方法, 应设置readOnly=true;
(3)使用timeout指定强制回滚之前事务可以占用的时间;
(4)完整使用如下:
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=3)
@Override
public void purchase(String username, String isbn) {
//1. 获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新数的库存
bookShopDao.updateBookStock(isbn);
//3. 更新用户余额
bookShopDao.updateUserAccount(username, price);
}