1、事务执行的时候是在前面开启事务,后面关闭事务,结束事务有两种方式,一种是正常的提交事务,一种是出现问题回滚事务。
spring事务默认只有在抛出unchecked Exception才会回滚
UncheckedException包括error和runtimeException派生出的所有子类
2、什么时候才用事务?
对数据库的数据进行批量或连表操作时,为了保证数据的一致性和正确性,我们需要添加事务管理机制进行管理。当对数据库的数据进行操作失败时,事务管理可以很好保证所有的数据回滚到原来的数据,如果操作成功,则保证所有需要更新的数据持久化。
例如:
1.转账:A对B转账,需要先把A的钱减掉再把B的钱加上,这两个操作任何一个失败,都要撤销整个转账过程,不然会出大问题。
2.向数据库中插入订单时,先插入订单再插入订单项,这两个操作任何一个失败都会影响数据的一致性,所以必须做事务的处理
3、Spring事务的配置
声明式事务(不需要开启注解代理等等)
配置通知(说明哪些方法需要拦截,被拦截的方法将应用配置好的事务属性)
配置切入点需要对哪个类进行事务管理
注意:只会管理通知中有的那些方法
4.事务的四大特性
ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子:A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败,在转账的过程中,数据要一致,A扣除了500,B必须增加500,在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰,在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)
一. Spring支持编程式事务管理和声明式事务管理两种方式
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。但是个人推荐用xml配置文件的方式。
二. Spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
事务的隔离级别都是用来解决事务并发的问题的:
脏读:一个事务读到另外一个事务还没有提交的数据。
不可重复读:一个事务先后读取同一条记录,但是两次读取的数据不同,称为不可重复读。
幻读:一个事务按照条件查询,没有对应的数据行,但是在插入数据的时候,又发现这行数据已经存在了,好像出现了。
1.事务的隔离级别(×就是解决的问题,勾就是未解决):
串行化就是说一个事务必须要全部完成才能开始下一个事务
2.事务的传播行为
PROPAGATION_REQUIRED-- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS-- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY-- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION _NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER-- 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED-- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则与PROPAGATION REQUIRED类似的操作。
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ExampleService {
@Autowired
private ExampleRepository exampleRepository;
@Transactional(propagation = Propagation.REQUIRED)
public void doRequiredPropagation() {
// 在当前事务中执行操作
exampleRepository.save(entity1);
exampleRepository.update(entity2);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doRequiresNewPropagation() {
// 开启一个新的事务执行操作,与外部事务独立
exampleRepository.save(entity1);
exampleRepository.update(entity2);
}
@Transactional(propagation = Propagation.NESTED)
public void doNestedPropagation() {
// 创建一个嵌套事务,在外部事务的上下文中执行
exampleRepository.save(entity1);
exampleRepository.update(entity2);
}
// 其他传播行为的示例方法类似...
}
3.undo log和redo log的区别
redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作;redo log保证了事务的持久性,undo log保证了事务的原子性和一致性。
4.事务中的隔离性是如何保证的呢?(你解释一下MVCC)
事务的隔离性是由锁和mvcc实现的。其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表。readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生ReadView,后续复用