参考博客:https://www.jianshu.com/p/603b52d2ae4b
- 基本知识.
事务是数据库层面的一个概念,它能够保证每一次对于数据库的操作都具有唯一性,它要么成功,要么就失败。具体来说,举一个比较普遍的例子: Person A给Person B转账10000元钱,这件事情的触发会导致A的银行账户里面减少10000元钱,B的银行账户里面增加10000块钱。但是如果遇到突发状况,当A的10000元钱扣掉的时候,网络中断了或者系统崩溃了,B并没有增加这10000块钱,但是A的10000块钱实实在在地消失了。 这个例子很明显是一个失败的业务案例,应该加上事务控制,也就是说,这个业务必须一次性走完所有流程全部成功,要么这个业务就失败,撤销掉,回到执行之前的状态以保护数据的安全性! (虽然和数据库层面的事务没什么关系,但是之前在搞flyway的时候,我在一个flyway脚本里面删除了table的一个属性,又重新加了另一个属性,但是我加属性的这条SQL写错了,flyway执行完了以后会报错,这条脚本执行失败。然而,第一条的删除语句是执行了的,也就是说即便在history里面把这条脚本给改正确,再执行也会报错说找不到要删除的这个属性!所以flyway里面写脚本,删除和增加字段就应该隔离开,参考事务的隔离性!)
事务有4个特性(ACID): - atomicity:原子性 事务是数据库最基本的逻辑单位,要么全部执行成功,要么全部不执行。个人认为理解这个概念对于事务的了解是最重要的。 - consistency:一致性 事务完成时,必须是所有的数据都保持一致性。在数据库中,所有规则都必须应用于事务的修改,以保证数据的完整性。(A少了10000,B就增加10000,总数保持不变) - isolation:隔离性 一个事务的执行不能被其他事务影响 - durability:持久性 事务提交了之后就永远留在了DB中,即便遇到各种突发情况数据都不会丢失提交事务的相关操作。
-
java事务 java有三种事务:jdbc事务、jta事务(java transaction api)、容器事务。 · JDBC事务: 操作流程: 1)获取JDBC连接->2)声明SQL->3)预编译SQL->4)执行SQL->5)处理结果集-> 6)释放结果集->7)释放Statement->8)提交事务->9)处理异常并回滚事务->10)释放JDBC连接 JDBC优缺点:1.冗长、重复 2.显示事务控制 3.每个步骤不可获取 4.显示处理受检查异常 5.不可操作多个数据库 -
Spring容器事务 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。 要么调用commit()方法、或者rollback()方法。
Public interface PlatformTransactionManager{
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
Void commit(TransactionStatus status) throws TransactionException;
Void rollback(TransactionStatus status) throws TransactionException;
}
通过getTransaction获取事务对象,传参为TransactionDefinition,TransactionDefinition定义了一些基本的事务属性。根据这篇博客所言,事务属性就是事务的一些基本配置,描述了事务策略如何引用到方法上。 事务属性包含了5个方面: - 传播行为、隔离规则、回滚事务、事务超时、是否只读。 TransactionDefinition: public interface TransactionDefinition { int getPropagationBehavior(); // 返回事务的传播行为 int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据 int getTimeout(); // 返回事务必须在多少秒内完成 boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的 }
- PROPOGATION传播行为有以下7种:
· PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
· PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
· PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
· PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
· PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
· PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
虽然有7种,但是常用的就第一种REQUIRED和第四种REQUIRES_NEW。
- ISOLATION事务的隔离级别有以下5种:
ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应;
ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
**ISOLATION_SERIALIZABLE**:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读。
- 事务的属性可同通过注解方式或配置文件配置:
@Transactional:
@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.(重点!!!)
默认情况下,一个有事务方法, 遇到RuntimeException 时会回滚 . 遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常})
@Transactional(
readOnly = false,
timeout = -1 ,
noRollbackFor = ArithmeticException.class,
isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED
)
下面是一个在SpringBoot中添加事务的例子: 这是一个班级管理的mode,在删除班级的时候,会先删除班级,然后把学生的班级信息置为null,分组状态设置为未分组。 在删除班级的时候,如果设置student对象的属性的时候出了问题,会抛出异常,但是这个时候虽然抛出异常,class已经被删除掉了,数据库已经没有关于这个class的信息了。所以开启事务,让整个过程要么成功,要么失败。这个时候,如果删除了班级之后出现什么问题的话,会撤回到提交之前的状态。
@Transactional(rollbackFor=Exception.class)
public void deleteClassById(Long classId) {
ClassInfoDo classInfoDo = classDomainService.findClassById(classId);
List<StudentInfoDo> studentInfoDos = studentDomainService.findAll().stream()
.filter(item -> Objects.equals(item.getClassId(), classInfoDo.getClassId()))
.collect(Collectors.toList());
List<TeacherInfoDo> teacherInfoDos = teacherDomainService.findAll().stream()
.filter(item -> Objects.equals(item.getClassId(), classInfoDo.getClassId()))
.collect(Collectors.toList());
classDomainService.deleteClassById(classId);
studentInfoDos.stream()
.peek(StudentInfoDo::setPropertiesToNull)
.collect(Collectors.toList());
teacherInfoDos.stream()
.peek(TeacherInfoDo::setPropertiesToNull)
.collect(Collectors.toList());
studentDomainService.saveAll(studentInfoDos);
teacherDomainService.saveAll(teacherInfoDos);
}
|