Spring事务行为

title: Spring事务行为
date: 2017-12-08 21:15:53
tags:
  - Java
  - Spring
categories: Spring

事务

特性

原子性:事务是一个不可分割的单位,事务中的操作要么都发生,要么都不发生。
一致性:事务执行前后数据的完整性必须保持一致
隔离性:多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离
持久性:一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响

隔离级别

隔离级别引发的问题:

  • 脏读:一个事务读取到了另一个事务改写但还未提交的数据(如果这些数据被回滚,则读到的数据是无效的)
  • 不可重复读:一个事务执行两次相同的查询,中间另一个事务更新了数据,前后两次读到的数据内容会不一致
  • 幻读:一个事务中执行两次相同的查询,中间另一个事务增删了数据,前后两次查询出的数据(规模)行数不一致

不可重复读 & 幻读区别:

  • 不可重复读强调数据内容的一致性,幻读强调数据内容、规模的一致性
  • 如果以悲观锁解决不可重复读和幻读:
    • 解决不可重复读只需要在事务中对数据行加锁,避免其他事务修改即可
    • 解决幻读需要在事务中加表锁,避免其他事务增加、删除数据

隔离级别:

隔离级别 含义 脏读 不可重复读 幻读 备注
READ_UNCOMMITED 允许读还未提交的改变了的数据
READ_COMMITED 允许在并发事务提交后读取 Oracle 默认
REREATABLE_READ 对相同字段的多次读取是一致的,除非数据被本事务改变 MySQL默认
SERIALIZABLE 完全服从 ACID 的隔离级别

MySQL 与 幻读

MySQL InnoDB 在默认事务(REREATABLE_READ)下可解决不可重复读,不保证避免幻读,可以使用间隙锁(next-key locks)来解决幻读。可以参考以下示例 MySQL的InnoDB的幻读问题:

表结构 & 事务级别:

mysql> show create table t_bitfly\G;
CREATE TABLE `t_bitfly` (
`id` bigint(20) NOT NULL default '0',
`value` varchar(32) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+

幻读场景一:

t Session A                   Session B
|
| START TRANSACTION;          START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| empty set
|                             INSERT INTO t_bitfly
|                             VALUES (1, 'a');
|
| SELECT * FROM t_bitfly;
| empty set
|                             COMMIT;
|
| SELECT * FROM t_bitfly;
| empty set
|
| INSERT INTO t_bitfly VALUES (1, 'a');
| ERROR 1062 (23000):
| Duplicate entry '1' for key 1
v (shit, 刚刚明明告诉我没有这条记录的)

幻读场景二:

t Session A                  Session B
|
| START TRANSACTION;         START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                            INSERT INTO t_bitfly
|                            VALUES (2, 'b');
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                            COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|
| UPDATE t_bitfly SET value='z';
| Rows matched: 2  Changed: 2  Warnings: 0
| (怎么多出来一行)
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | z     |
| |    2 | z     |
| +------+-------+
|
v

间隙锁解锁幻读

t Session A                 Session B
|
| START TRANSACTION;        START TRANSACTION;
|
| SELECT * FROM t_bitfly
| WHERE id<=1
| FOR UPDATE;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                           INSERT INTO t_bitfly
|                           VALUES (2, 'b');
|                           Query OK, 1 row affected
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                           INSERT INTO t_bitfly
|                           VALUES (0, '0');
|                           (waiting for lock ...
|                           then timeout)
|                           ERROR 1205 (HY000):
|                           Lock wait timeout exceeded;
|                           try restarting transaction
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
|                           COMMIT;
|
| SELECT * FROM t_bitfly;
| +------+-------+
| | id   | value |
| +------+-------+
| |    1 | a     |
| +------+-------+
v

共享锁与排他锁

t Session A                      Session B
|
| START TRANSACTION;             START TRANSACTION;
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|                                INSERT INTO t_bitfly
|                                VALUES (2, 'b');
|                                COMMIT;
|                (Session B 提交)
|
| SELECT * FROM t_bitfly;
| (普通查询)
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
|
| SELECT * FROM t_bitfly LOCK IN SHARE MODE;
| (加共享锁查询:事务对数据行加共享锁后,可进行读写操作;
|  其他事务可对该数据加共享锁,但不能加排他锁,且只能读数据,不能修改数据)
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly FOR UPDATE;
| (排他锁查询:事务对数据加上排他锁后,其他事务不能对该数据加任何的锁。
   获取排他锁的事务既能读取数据,也能修改数据)
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| |  2 | b     |
| +----+-------+
|
| SELECT * FROM t_bitfly;
| +----+-------+
| | id | value |
| +----+-------+
| |  1 | a     |
| +----+-------+
v

Spring 与事务

接口

Spring 事务管理高层抽象主要的接口有:

  • 事务管理器:PlatformTranscationManager
  • 事务定义信息:TransactionDefinition
  • 事务具体运行状态:TransactionStatus

使用 TransactionDefinition 定义事务信息,由 PlatformTransactionManager 负责执行事务,执行的结果记录在 TransactionStatus。

PlatformTransactionManager

包含多个实现,可以为不同持久化框架提供不同实现

实现 说明
DataSourceTransactionManager 使用 Spring JDBC 或 MyBatis 进行持久化数据时使用
HibernateTransactionManager 使用 Hibernate 进行持久化数据时使用
JpaTransactionManager 使用 JPA 进行持久化时使用
JdoTransactionManager Jdo 持久化机制时使用
JtaTransactionManager 使用 JTA 管理事务

TransactionDefinition

  • 常量

    ISOLATION_XXX 定义了事务的隔离级别

    PROPAGATION_XXX 定义了事务的传播行为

    TIMEOUT_DEFAULT 默认超时

  • 方法

    获得事务以上信息

隔离级别

Spring 事务隔离级别所有事务隔离级别,默认使用 DB 的事务隔离级别

传播行为

传播行为解决事务方法相互调用时,事务的处理方式。Spring 事务提供的传播行为:

事务传播行为 含义
PROPAGATION_REQUIRED 支持当前事务,如果不存在就新建一个
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务(保存点)
PROPAGATION_REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
  methodB();
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
}

methodB();  // 当前上下文不存在事务,methodB 开启一个新的事务
methodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 发现当前上下文有事务,因此就加入到当前事务中来
PROPAGATION_SUPPORTS
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
  methodB();
}

@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
}

methodB();  // methodB 以非事务的方式执行
mtthodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 加入 methodA 的事务
PROPAGATION_MANDATORY
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
  methodB();
}


// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.MANDATOR)
public void methodB() {
}

methodB();  // 当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
methodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 加入 methodA 的事务
PROPAGATION_REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
  doSomeThing1();
  methodB();
  doSomeThing2();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
}

当调用 methodB() ,当前上下文不存在事务,methodB 开启一个新的事务

当调用 methodA() 相当于执行了以下的代码:


TransactionManager tm = null;
try{
  // 获得一个JTA事务管理器
  tm = getTransactionManager();
  tm.begin();// 开启一个新的事务
  Transaction ts1 = tm.getTransaction();
  doSomeThing1();
  tm.suspend1();// 挂起当前事务
  try{
    tm.begin();// 重新开启第二个事务
    Transaction ts2 = tm.getTransaction();
    methodB();
    ts2.commit();// 提交第二个事务
  } Catch(RunTimeException ex) {
    ts2.rollback();// 回滚第二个事务
  } finally {
    // 释放资源
  }
  // methodB执行完后,恢复第一个事务
  tm.resume(ts1);
  doSomeThing2();
  ts1.commit();// 提交第一个事务
} catch(RunTimeException ex) {
  ts1.rollback();// 回滚第一个事务
} finally {
  // 释放资源
}

上面的 ts1 和 ts2 是两个独立的事务,互不干扰, ts2 是否成功并不依赖于 ts1。如果 methodA 在调用 methodB 方法后的 doSomeThing2 发生异常,methodB 并不受影响结构依然没提交,但 methodA 的其他代码则会被回滚。

使用 PROPAGATION_REQUIRES_NEW 需要使用 JtaTransactionManager 作为事务管理器。

PROPAGATION_NOT_SUPPORTED
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
  doSomeThing1();
  methodB();
  doSomeThing2();
}

@Transactional(propagation = Propagation.PROPAGATION_NOT_SUPPORTED)
public void methodB() {
}

总是以非事务的形式执行,当 methodA 执行到 methodB(); 时先挂起事务,再执行 methodB(), 完成后再恢复 methodA 的事务继续执行 doSomeThing2 方法。

使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

PROPAGATION_NEVER

总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED
@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){
  doSomeThing1();
  methodB();
  doSomeThing2();
}

@Transactional(propagation = Propagation.NEWSTED)
public void methodB(){
}

当调用 methodB() ,则按照 PROPAGATION_REQUIRED 执行,当前上下文不存在事务,methodB 开启一个新的事务

当调用 methodA() 相当于执行了以下的代码:

Connection con = null;
Savepoint savepoint = null;
try{
  con = getConnection();
  con.setAutoCommit(false);
  doSomeThing1();
  savepoint = con2.setSavepoint();
  try{
    methodB();
  } catch(RuntimeException ex) {
    con.rollback(savepoint);
  } finally {
    //释放资源
  }
  doSomeThing2();
  con.commit();
} catch(RuntimeException ex) {
  con.rollback();
} finally {
  //释放资源
}

在调用 methodB 之前,先调用 setSavepoint 方法,保存当前的状态到 savepoint。如果 methodB 执行失败,则恢复到 savepoint 保存的状态。

如果外层事务执行失败,会回滚内层事务所做的动作;

如果内层事务执行失败,不会引起外层事务的回滚;

使用JDBC 3.0驱动时,仅仅支持 DataSourceTransactionManager 作为事务管理器,PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)

TransactionStatus

维护,获取事务的各种状态

使用 Spring 事务

Spring 提供编程式和声明式的事务管理,具体的代码实现可以参考:spring-tx-test

你可能感兴趣的:(Spring事务行为)