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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容