事务在代码里或者数据库中都可以配置。
其含义理解为 一系列的数据操作,要么全部执行完成、要么都不执行。归纳为
1、原子性:事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
2、一致性:一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败
3、隔离性:事务之间应该隔离开来。因为可能有许多事务会同时处理相同的数据,每个事务都应该与其他事务有隔离策略。
4、持久性:一旦事务完成,它的结果不会收到影响。通常情况下,事务的结果被写到持久化存储器中。
spring 在事务处理上支持编程式事务和声明式事务
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
这些属性在TransactionDefinition接口中定义,常用事务传播规则如下:
常量名称 | 含义 |
---|---|
PROPAGATION_REQUIRED | Spring默认的传播机制,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行 |
PROPAGATION_REQUES_NEW | 1、该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。2、如果外层没有事务,执行当前新开启的事务。注意的是。新事物和挂起的事务是两个独立的事务。外层事务失败回滚不会导致内层事务回滚,内层事务失败回滚抛出异常,外层也可以不处理。 |
PROPAGATION_SUPPORT | 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。简称:完全依赖外层的事务 |
PROPAGATION_NOT_SUPPORT | 该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码 |
PROPAGATION_NEVER | 该传播机制不支持外层事务,即如果外层有事务就抛出异常 |
PROPAGATION_MANDATORY | 与NEVER相反,如果外层没有事务,则抛出异常 |
PROPAGATION_NESTED | 该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。 |
使用注解@Transactional 定义事务传播机制:
@Transactional(propagation = Propagation.REQUIRED)
propagation属性值还可以取:
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
思考:在同一个类中,没有被声明事务的方法A 去调用有事务的方法B,但B中出现异常,那么事务B会回滚吗?
@Test
public void test(){
update();
}
@Transactional
public void update(){
Blog blog=mapper.selectById(1);//原先是 测试
blog.setTitle("事务测试");
blog.setType(1);
mapper.updateById(blog);
int i=1/0;
}
执行test方法,update方法中报异常,但发现事务并没有回滚。
发现数据还是更改了
这是为什么呢?
Spring事务管理用的是AOP,AOP底层用的是动态代理。所以如果我们在类或者方法上标注注解@Transactional
,那么会生成一个代理对象 proxy。
我们执行的也是代理对象中的tes() 方法,但是test方法中 的update()方法还是指向代理之前的update方法。而不是代理后被事务管理的update方法。
所以此时 没有事务的参与。
如果说,在不同类中,A方法调用 有事务管理的B,那么B中发生异常,是可以回滚的。
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁
表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
事务的隔离级别有以下几种:
数据库隔离级别 | spring 中属性值 | 含义 |
---|---|---|
ISOLATION_DEFAULT | 使用后端数据库的默认隔离级别 | |
Read Uncommitted | ISOLATION_READ_UNCOMMITTED | (读取未提交内容)允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。 |
Read Committed | ISOLATION_READ_COMMITTED | (读取提交内容)(Oracle 默认级别)允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。 |
Repeatable Read | ISOLATION_REPEATABLE_READ | (可重读)(MYSQL默认级别)对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。 |
Serializable | ISOLATION_SERIALIZABLE | (可串行化)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。 |
1、脏读现象
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的、错误的。
2、不可重复读现象
一个事务执行两次重复的读取,两次读取的数据都不一样,这通常是另一个事务并发的改变了数据(不是回滚产生的改变数据)
主要指数据发生修改
3、幻读
一个事务A读取数据时/后,另一个事务B插入/删除了一些数据,下次事务A读取事务时,就会发现比第一次多/少了值。
在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚(这一行为和EJB中的回滚行为是一致的)。
不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
事物配置中有哪些属性可以配置?以下只是简单的使用参考
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。