事务解析—@Transational

1,518 阅读6分钟

事务介绍

概念

在 关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。

事务是恢复和 并发控制的基本单位。

ACID

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性

原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。

一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

事务的并发问题

1.脏读

事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。

2.不可重复读

事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

3.幻读

系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

事务隔离级别

1. Read uncommitted(读未提交)

如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据。

解决了更新丢失,但还是可能会出现脏读

2. Read committed(读提交)

如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

解决了更新丢失和脏读问题

3. Repeatable read(可重复读取)

可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

解决了更新丢失、脏读、不可重复读、但是还会出现幻读

4. Serializable(可序化)

提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读

解决了更新丢失、脏读、不可重复读、幻读(虚读)

5. Default(默认隔离级别)

大多数数据库如Oracle是RC。

Mysql的隔离级别默认为RR。

@Transational 解析

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor(“transactionManager”)
    String value() default “”;

    @AliasFor(“value”)
    String transactionManager() default “”;

    Propagation propagation() default Propagation.*REQUIRED*;

    Isolation isolation() default Isolation.*DEFAULT*;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

Target

@Transactional 注解也可以添加到类级别上,也可以添加到方法级别上。 当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。 当类级别配置了@Transactional,方法级别也配置了@Transactional,应用程序会以方法级别的事务属性信息来管理事务,也就是方法级别的事务属性信息会覆盖类级别的相关配置信息。

Propagation

  • PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

  • PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。

  • PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

Isolation

DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);

详情同隔离级别.

readOnly

设置事务的只读属性,如果设置为true,可以自动提供优化手段。

Rollback

  • rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
  • rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
  • noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
  • noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

在@Transactional注解中如果不配置rollbackFor属性, 只在发生不受控异常(RuntimeException和Error)时才进行事务回滚。

@Transactional(rollbackFor=Exception.class), 如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚。

同理,配置noRollback后,可以指定遇到特点的异常不回滚。

@Transactional 常见不起作用的场景

  1. 修饰方法非Public.

  2. 类内部调用调用类内部@Transactional标注的方法。

  3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。

  4. 被@ Transactional修饰的方法内部调用了@Async,导致不在同一个线程中了。