一、Spring事务管理
`Spring`事务管理的本质就是封装了数据库对事务支持的操作,使用JDBC的事务管理机制,就是利用`java.sql.Connection`对象完成对事务的提交和回滚。
概念:逻辑上的一组操作,组成该组操作的每个单元,要么同时成功,要么同时失败。
Connection conn = DriverManager.getConnection();
try {
// 自动提交设置为false
conn.setAutoCommit(false);
// 执行增删改查操作
// 当操作成功后手动提交
conn.commit();
} catch (Exception e) {
// 出现异常,回滚所有操作
conn.rollback();
e.printStackTrace();
} finally {
conn.colse();
}
2、事务常见概念
-
事务
事务是指作为单个逻辑工作单元执行的一系列操作(SQL语句)。这些操作要么全部成功,要么全部不成功。
-
特性:ACID
原子性(Atomicity):事务中的多个操作要么都成功要么都失败
一致性(consistency):事务的执行的前后数据的完整性保持一致
隔离性(isolation):事务执行的过程中,不应该受到其他事务的干扰
持久性(durability):事务一旦结束,数据就持久到数据库
-
隔离问题
**脏读**:一个事务读到另一个事务没有提交的数据
所谓脏读,就是指**事务A读到了事务B还没有提交的数据**,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
**不可重复读**:一个事务前后多次读取相同数据,数据内容不一致,update场景问题
所谓不可重复读,就是指**在一个事务里面读取了两次某个数据,读出来的数据不一致**。还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
虚读(幻读):一个事务前后多次读取,数据总量不一致,insert场景问题
所谓幻读,就是指**在一个事务里面的操作中发现了未被操作的数据**。比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
- 隔离级别
-
补充:
-
事务隔离级别为ISOLATION_READ_UNCOMMITTED时,写数据只会锁住相应的行。
-
事务隔离级别为可ISOLATION_REPEATABLE_READ时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
-
事务隔离级别为ISOLATION_SERIALIZABLE时,读写数据都会锁住整张表。
-
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也就越大。
-
-
不考虑隔离性引发的安全问题
`read uncommitted`:
事务可以读取另一个未提交事务的数据。
`read committed`:
事务要等另一个事务提交后才能读取数据,解决脏读。
`repeatable read`:
在开始读取数据时,事务开启,不再允许修改操作,解决:脏读、不可重复读。
`serializable`:
最高事务隔离级别,事务串行化顺序执行,解决脏读、不可重复读、幻读。但是效率低下,耗数据库性能。
3、事务管理API描述
-
PlatformTransactionManager
平台事务管理器,Spring管理事务,必须使用事务管理器进行事务配置时,核心方法:获取事务,提交事务,回滚事务。
-
TransactionDefinition
该对象封装事务详情(事务定义、事务属性),例如:隔离级别、是否只读、超时时间 等。
-
TransactionStatus
用于记录当前事务运行状态。例如:是否有保存点,事务是否完成。Spring底层根据状态进行相应操作。
4、事务案例SQL语句
CREATE TABLE user_account(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
money INT
) ENGINE=InnoDB DEFAULT CHARSET=utf8;;
INSERT INTO user_account(username,money) VALUES('jack','5000');
INSERT INTO user_account(username,money) VALUES('tom','5000');
SELECT * FROM user_account ;
5.事务的传播行为
TransactionDefinition.PROPAGATION_NEVER | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
TransactionDefinition.PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。 |
TransactionDefinition.PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
TransactionDefinition.PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
TransactionDefinition.PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。 |
TransactionDefinition.PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。 |
TransactionDefinition.PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
6.回滚规则
事务回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚。而在遇到检查型异常时不会回滚。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
7.事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,也会占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
8.是否只读
如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据, 这个时候我们应该给该事务设置只读属性,这样可以帮助数据库引擎优化事务。提升效率。
二、@Transactional使用
Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式:
-
编程式事务:允许用户在代码中精确定义事务的边界。编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
-
声明式事务: 基于AOP,有助于用户将操作与事务规则进行解耦。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理也有两种常用的方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional注解的方式。显然基于注解的方式更简单易用,更清爽。@Transactional注解的使用也是我们本文着重要理解的部分。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
2.1 @Transactional注解属性
@Transactional注解里面的各个属性和咱们在上面讲的事务属性里面是一一对应的。用来设置事务的传播行为、隔离规则、回滚规则、事务超时、是否只读。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
*/
@AliasFor("transactionManager")
String value() default "";
/**
* 同上。
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 事务的传播行为,默认值为 REQUIRED。
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事务的隔离规则,默认值采用 DEFAULT。
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 事务超时时间。
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 是否只读事务
*/
boolean readOnly() default false;
/**
* 用于指定能够触发事务回滚的异常类型。
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 同上,指定类名。
*/
String[] rollbackForClassName() default {};
/**
* 用于指定不会触发事务回滚的异常类型
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 同上,指定类名
*/
String[] noRollbackForClassName() default {};
}