1. 事务概述
●在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
●事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
事务的四个关键属性(ACID)
- 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
- 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
- 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
- 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
2. Spring事务管理
2.1 编程式事务管理
①使用原生的JDBC API进行事务管理
- 获取数据库连接Connection对象
- 取消事务的自动提交
- 执行操作
- 正常完成操作时手动提交事务
- 执行失败时回滚事务
- 关闭相关资源
TransactionFilter{
try{
//获取连接
//设置非自动 提交
chain.doFilter();
//提交
}catch(Exception e){
//回滚
}finllay{
//关闭连接释放资源
}
}
②评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
2.2 声明式事务管理
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
Spring既支持编程式事务管理,也支持声明式的事务管理。
2.3 Spring提供的事务管理器
Spring的核心事务管理抽象是PlatformTransactionManager,它可以在目标方法运行前后进行事务控制(事务切面)
2.4 快速为某个方法添加事务
①配置出这个事务管理器使其工作
②开启基于注解的事务
③给事务方法加@Transactional注解
<!-- 事务控制 -->
<!--1:配置事务管理器(切面)让其进行事务控制,须导入面向切面编程的几个包
spring-aspects-4.0.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住数据源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--2:开启基于注解的事务控制模式;依赖tx名称空间 -->
<tx:annotation-driven transaction-manager="tm"/>
<!--3:给事务方法加注解@Transactional -->
@Repository
public class BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updataBanlace(String username, int price){
String sql = "UPDATEAA account SET balance=balance-? WHERE username=?";
jdbcTemplate.update(sql, price, username);
}
public int getPrice(String isbn){
String sql = "select price from book where isbn=?";
Integer queryForObject = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return queryForObject;
}
public void updateStock(String isbn){
String sql ="update book_stock set stock=stock-1 where isbn=?";
jdbcTemplate.update(sql, isbn);
}
}
@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional
public void checkout(String username, String isbn){
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
bookDao.updataBanlace(username, price);
}
}
有事务的业务逻辑(Service),容器中保存的是这个业务逻辑的代理对象,即代理对象执行业务方法
2.5 Transactional的配置项(属性)
①timeout-int
(秒为单位)——超时:事务超出指定执行时长后自动终止并回滚
@Transactional(timeout=3)
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2、减余额
bookDao.updateBalance(username, price);
//int i = 10/0;
}
②事务回滚:默认发生运行时异常都回滚,发生编译时异常不会回滚
rollbackFor-Class[]
——原本不回滚(默认编译时异常不回滚)的异常指定其回滚;
@Transactional(timeout=3,readOnly=false,rollbackFor={FileNotFoundException.calss})
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2、减余额
bookDao.updateBalance(username, price);
//会回滚
new FileInputStream("D://hahahahha.aa");
}
noRollbackFor-Class[]
——哪些异常事务可不回滚(可让原来默认回滚的异常不回滚)
@Transactional(timeout=3,readOnly=false,noRollbackFor={ArithmeticException.calss,NullPointerException.class})
public void checkout(String username,String isbn){
//1、减库存
bookDao.updateStock(isbn);
int price = bookDao.getPrice(isbn);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2、减余额
bookDao.updateBalance(username, price);
//不回滚
int i = 10/0;
}
异常分类:
- 运行时异常(非检查异常):可不用处理;默认都回滚
- 编译时异常(检查异常):要么try-catch,要么在方法上声明throws;默认不回滚;
③isolation-Isolation
事务的隔离级别
数据库事务并发问题
假设现有两个事务:Transaction01和Transaction02并发执行。
- 脏读
[1]Transaction01将某条记录的AGE值从20修改为30。
[2]Transaction02读取了Transaction01更新后的值:30。
[3]Transaction01回滚,AGE值恢复到了20。
[4]Transaction02读取到的30就是一个无效的值。 - 不可重复读
[1]Transaction01读取了AGE值为20。
[2]Transaction02将AGE值修改为30。
[3]Transaction01再次读取AGE值为30,和第一次读取不一致。 - 幻读
[1]Transaction01读取了STUDENT表中的一部分数据。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01读取了STUDENT表时,多出了一些行。
隔离级别——数据库系统须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
- 读未提交——READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。 - 读已提交——READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。 - 可重复读——REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。(mysql下可避免所有问题) - 串行化——SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
REPEATABLE READ举例:
3. 事务的传播行为
1.传播行为
propagation-Propagation
——事务的传播行为;当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
AService{
tx_a(){
...
tx_b(){
}
...
tx_c(){
...
}
}
}
Spring定义了7种类传播行为:
例如:
@Transactional
public void mulTx(){
//传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(使用同一条连接);
//REQUIRED
bookService.checkout("Tom", "ISBN-001");
//REQUIRED REQUIRES_NEW
bookService.updatePrice("ISBN-002", 998);
//int i = 10/0;
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn, price);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn){
bookDao.updateStock(isbn);
bookDao.updateBalance(username, bookDao.getPrice(isbn));
}
2. 原理
练习:
multx(){
//REQUIRED
A(){
//REQUIRES_NEW
B(){}
//REQUIRED
c(){}
}
//REQUIRES_NEW
D(){
DDDD()// REQUIRES_NEW不崩,REQUIRED崩
//REQUIRED
E(){
//REQUIRES_NEW
F(){
10/0(E,G,D,A,C崩了)
}
}
//REQUIRES_NEW
G(){}
}
任何处崩,已执行的REQUIRES_NEW都会成功;
如果是REQUIRED;事务的属性都是继承于大事务的;
本类方法的嵌套调用就只是一个事务;
3. 基于XML配置的事务
①Spring中提供事务管理器(事务切面),配置这个事务管理器
②配置出事务方法
③告诉Spring哪些方法是事务方法
(事务切面按照我们的切入点表达式去切入事务方法)
<!-- 基于xml配置的事务;依赖tx名称空间和aop名称空间-->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
<!--
事务建议/事务增强
advice-ref:指向事务管理器的配置
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事务管理器
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;
切入点表达式只是说,事务管理器要切入这些方法,
哪些方法加事务——使用tx:method指定 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 基于注解的和配置方法的都使用:重要的用配置,不重要的用注解 -->