Spring事务传播与隔离级别

什么是事务

  1. 事务就是一组操作数据库的动作集合。
  2. 动作集合被完整地执行,我们称该事务被提交。动作集合中的某一部分执行失败,整个动作集合提交失败,回到最初的状态,称为事务回滚。
  3. 事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

事务的特性

  1. 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。(操作)
  2. 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。(数据)
  3. 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。(数据)
  4. 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。(数据)

spring事务管理核心接口

Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。

image

事务管理器

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是
org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

Public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
} 

从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。

1.JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2.Hibernate事务

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

基本事务属性的定义

上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面:

  1. 传播行为
  2. 隔离规则
  3. 回滚规则
  4. 事务超时
  5. 是否只读
image

TransactionDefinition接口内容如下:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 

事务传播行为(重点)

什么是事务传播?

事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

public void methodA(){
    methodB();
    //doSomething
     }
     
     @Transaction(Propagation=XXX)
     public void methodB(){
    //doSomething
 }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。

spring事务传播行为有哪些?(重点掌握)

传播行为 含义
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,这是最常见的选择,也是默认的事务传播行为。
PROPAGATION_REQUIRED_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

什么是事务挂起?

例如方法A支持事务,方法B不支持事务,方法A调用方法B。
在方法A开始运行时,系统为它建立Transaction,方法A中对于数据库的操作,会在该Transaction的控制之下。
这时,方法A调用方法B,方法A打开的Transation将挂起,方法B中任何数据库操作,都不在该Transaction的管理之下。
当方法B返回,方法A继续运行,之前的Transaction恢复,后面的数据库操作继续在该Transaction的控制之下提交或回滚。

测试案例

用户实现类中包含注册和注册送积分两个业务方法:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private BSService bsService;
    //用户注册
    @Override
    public void registe(User user) {
    userMapper.insert(user);
    }
    //用户注册并送积分
    @Override
    public void registeAndCredit(User user){
        registe(user);
        Credit credit = new Credit();
        credit.setUsername(user.getName());
        credit.setScore(20);
        bsService.addCredit(credit);
    }
}

业务实现类中有送积分的业务方法:

@Service
public class BSServiceImpl implements BSService {
    @Autowired
    private CreditMapper creditMapper;
        //送积分
        @Override
        public void addCredit(Credit credit) {
        creditMapper.insert(credit);
        throw new RuntimeException();
    }
}

registeAndCredit方法中包含了用户注册和送积分两个数据库操作。

1.PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,这是最常见的选择,也是默认的事务传播行为。

场景一:registeAndCredit方法不添加事务,registe方法和addCredit方法均添加PROPAGATION_REQUIRED事务,addCredit方法抛出异常。

 //送积分
@Override
@Transactional
public void addCredit(Credit credit) {
    creditMapper.insert(credit);
    throw new RuntimeException();
}
//用户注册
@Override
@Transactional
public void registe(User user) {
userMapper.insert(user);
}
@Test
public void test03(){
    User user = new User();
    user.setName("小红");
    user.setAge(26);
    userService.registeAndCredit(user);
}

测试结果:用户注册成功,送积分失败。
测试分析:因为registeAndCredit没有添加事务,对于registe和addCredit来说,属于当前没有事务,所以各自新建事务,两个事务是独立的,互不影响。

场景二:registeAndCredit方法添加PROPAGATION_REQUIRED事务。对于registe和addCredit来说,属于存在事务,是否添加PROPAGATION_REQUIRED均属于同一个事务。

如果事务中异常被try catch处理后,事务正常提交。(嵌套调用方法除外)
使用此事务,在事务A中又开了一个事务B,其实AB是同一种事务,当事务回滚B时已经将事务标记为回滚,如果在A中try catch之后,事务A再次执行commit会报异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only(事务已经被标记为回滚)
代码如下:

 //用户注册并送积分
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void registeAndCredit(User user){
    registe(user);
    Credit credit = new Credit();
    credit.setUsername(user.getName());
    credit.setScore(20);
    try {
        bsService.addCredit(credit);
    }catch (Exception e){
        System.out.println("%%%%%%%%%%%%%%%%%%%jiaixnxiao%%%%%%%%%%");
    }
}

//送积分
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addCredit(Credit credit) {
    creditMapper.insert(credit);
    throw new RuntimeException();
}

执行registeAndCredit抛出事务已经被标记为回滚的异常。

2.PROPAGATION_REQUIRED_NEW

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

PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。

场景一:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,并且抛出异常

  1. 如果不在registeAndCredit中对addCredit进行try catch的话,两个事务均回滚,因为addCredit抛出异常到registeAndCredit。(可以理解为addCredit开启新事物抛出异常并回滚,异常被当前事务registeAndCredit接收,因此也回滚)。
  2. registeAndCredit对addCredit进行try catch,新事物回滚,当前事务正常commit,所以用户注册成功,送积分操作回滚。

场景二:registeAndCredit添加PROPAGATION_REQUIRES_NEW事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,并且抛出异常,与场景一相同。

场景三:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_REQUIRES_NEW事务,在registeAndCredit中抛出异常,因为addCredit创建了新事务,是两个不同的事务,所以用户注册回滚,送积分提交成功。

3.PROPAGATION_NESTED

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

场景一:与PROPAGATION_REQUIRED_NEW场景一相同。
场景三:registeAndCredit添加PROPAGATION_REQUIRED事务,addCredit添加PROPAGATION_NESTED事务,在registeAndCredit中抛出异常。addCredit创建了registeAndCredit事务的嵌套事务,外围事务抛出异常回滚,嵌套事务也回滚。所以用户注册回滚,送积分回滚。

新事务与嵌套事务

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:PROPAGATION_REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,而 PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。

注意问题

  1. 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。如将addCredit设置为PROPAGATION_MANDATORY,如果controller直接调用addCredit方法,将引发一个异常。正确的情况是:addCredit方法必须被另一个带事务的业务方法调用。所以 PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。
  2. 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设addCredit设置为PROPAGATION_NEVER,当registeAndCredit拥有一个事务时,addCredit方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的。
  3. 当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容