http://blog.csdn.net/yerenyuan_pku/article/details/52885041

使用Spring注解方式管理事务

前面讲解了怎么使用@Transactional注解声明PersonServiceBean底下所有的业务方法需要事务管理,那么事务是如何来管理的呢? 
我们知道当每个业务方法执行的时候,它都会打开事务,在业务方法执行结束之后,它就会结束事务。那么它什么时候决定这个事务提交,什么时候决定这个事务回滚呢?原先我们手工控制事务的时候,通常这个事务的提交或回滚是由我们来操纵的,那现在我们采用容器的声明式事务管理,那我们如何知道事务什么时候提交,什么时候回滚呢?答案是:Spring容器默认情况下对运行时异常,它会进行事务的回滚,如果它碰到的是用户异常,如检查时异常(checked exception),这时事务就不会回滚。 
现在我们就来做一个实验。假设person表里面有如下记录: 
 
如果现在我们要删除person表中id为5的记录,但是在PersonServiceBean类的delete()方法中,人为地抛出一个运行时异常,如下:

public void delete(Integer personid) {
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER}); throw new RuntimeException("运行期异常");
}

此时测试PersonServiceTest类中的delete()方法:

@Test
public void delete() {
personService.delete(5);
}

会发现Eclipse控制台打印出一个异常,立马查看person表,发现id为5的记录没有删除掉,这就说明了Spring容器默认情况下对运行时异常,它会进行事务的回滚。 
如果在PersonServiceBean类的delete()方法中,人为地抛出一个检查时异常,如下:

public void delete(Integer personid) throws Exception {
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER}); throw new Exception("检查时异常");
}
  • 1

为了不报错,我们还须将PersonService接口中的delete()方法签名修改为:

/**
* 删除指定id的person
*/
public void delete(Integer personid) throws Exception;

此时测试PersonServiceTest类中的delete()方法:

@Test
public void delete() {
try {
personService.delete(5);
} catch (Exception e) {
e.printStackTrace();
}
}

会发现Eclipse控制台打印出一个异常,立马查看person表,发现id为5的记录被删除掉,这就说明了如果Spring容器碰到的是用户异常,如检查时异常(checked exception),这时事务就不会回滚。 
当然我们也可改变这种规则:

  • 当Spring容器碰到用户异常——如检查时异常(checked exception)时,让事务回滚。 
    那到底该怎么办呢?此时就需要用到事务的rollbackFor属性了。我们将PersonServiceBean类的delete()方法修改为:

    @Transactional(rollbackFor=Exception.class)
    public void delete(Integer personid) throws Exception {
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER}); throw new Exception("检查时异常");
    }
    • 1

    此时测试PersonServiceTest类中的delete()方法:

    @Test
    public void delete() {
    try {
    personService.delete(4);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    • 1

    会发现Eclipse控制台打印出一个异常,立马查看person表,发现id为4的记录没有被删除掉。

  • 当Spring容器碰到运行时异常时,不让它进行事务的回滚,而是提交事务。 
    那到底该怎么办呢?此时就需要用到事务的rollbackFor属性了。我们将PersonServiceBean类的delete()方法修改为:

    @Transactional(noRollbackFor=RuntimeException.class)
    public void delete(Integer personid) throws Exception {
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER}); throw new RuntimeException("运行期异常");
    }
    • 1

    此时测试PersonServiceTest类中的delete()方法, 会发现Eclipse控制台打印出一个异常,立马查看person表,发现id为4的记录已经被删除掉了。

事务传播属性

事务还有一些其他的特点,如在业务bean——PersonServiceBean中,有些业务方法是不需要进行事务管理的,比方说获取数据的方法,那么这个时候我们就需要用到事物的propagation属性了,该属性指定了事务的传播行为。所以,我们应将getPerson()方法的代码修改为:

/**
* 使用JdbcTemplate获取一条记录
*/
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public Person getPerson(Integer personid) {
return jdbcTemplate.queryForObject("select * from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER}, new PeronRowMapper());
}
  • 1

还要将getPersons()方法的代码修改为:

/**
* 使用JdbcTemplate获取多条记录
*/
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public List<Person> getPersons() {
return jdbcTemplate.query("select * from person", new PeronRowMapper());
}
  • 1

这样,当这2个业务方法执行的时候,它都不会开启事务,能节约资源,提供效率。 
下面,我们就来对事务传播属性做一个总结:

  • REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么这个时候就会加入到该事务中,如果当前没有事务环境的话,就会为自己创建一个新的事务。默认情况下,业务方法的事务传播属性就是REQUIRED。在应用开发中,80%的情况下都会使用这种事务传播属性。
  • NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用(在其他业务bean的方法中被调用了,而其他业务bean的方法是开启了事务的),该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
  • REQUIRESNEW:该属性表明不管当前是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
  • MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出异常。
  • SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。即当标注了事务传播属性——SUPPORTS的业务方法在另一个bean的业务方法中执行时,如果另一个bean的业务方法开启了事务,那么执行到标注了事务传播属性——SUPPORTS的业务方法时,它就会处在事务中执行,如果另一个bean的业务方法也没开启事务,那么标注了事务传播属性——SUPPORTS的业务方法也在没有事务的环境中进行
  • Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出异常,只有业务方法没有关联到任何事务,才能正常执行。
  • NESTED:如果一个活动的事务存在,则当前方法运行在一个嵌套的事务中。 如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效果。

接下来,我们着重介绍事务传播属性——NESTED。如有下面一段代码:

@Resource OtherService otherService;

public void xxx() {
stmt.executeUpdate("update person set name='888' where id=1");
otherService.update(); // OtherService的update()方法的事务传播属性为NESTED
stmt.executeUpdate("delete from person where id=9");
}
  • 1

将以上代码展开,可能就变成了如下这样一段代码:

Connection conn = null;
try {
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate("update person set name='888' where id=1"); Savepoint savepoint = conn.setSavepoint(); // 保存点
try{
conn.createStatement().executeUpdate("update person set name='222' where sid=2");
}catch(Exception ex){
conn.rollback(savepoint);
} stmt.executeUpdate("delete from person where id=9");
conn.commit();
stmt.close();
} catch (Exception e) {
conn.rollback();
}finally{
try {
if(null!=conn && !conn.isClosed()) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
  • 1

其中,OtherService中标注事务传播属性为NESTED的update()方法,就相当于这样一段代码:

Savepoint savepoint = conn.setSavepoint(); // 保存点
try{
conn.createStatement().executeUpdate("update person set name='222' where sid=2");
}catch(Exception ex){
conn.rollback(savepoint);
}
  • 1

我们也就明白了内部事务的回滚不会对外部事务造成影响。

事务的其他属性

除了事务传播属性外,事务还有一些其他的属性:

  • readOnly属性:设置为只读事务,对于只读事务,它就不能进行更新操作,一般只存在数据读取的时候,可以将readOnly属性设置为true,可提供效率。
  • timeout属性:代表事务的超时时间,默认为30s,一般情况下都不需要设置超时时间。

事务的isolation属性

事务的isolation属性指定了事务的隔离级别,实际上事务的隔离级别并不是由Spring容器决定的,而是由底层数据库决定的。

数据库系统提供了四种事务隔离级

数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,但对并发访问数据库的性能影响最大。Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read,如MySQL

  • Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)
  • Read Commited:读已提交数据(会出现不可重复读和幻读)
  • Repeatable Read:可重复读(会出现幻读)
  • Serializable:串行化

脏读:一个事务读取到另一事务未提交的更新新据。前提是并发的两个或多个事务。 
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。目前要实现可重复读的话,一般数据库采用快照技术,在某一时刻(点),当你访问数据的时候,它把这个数据作为一个镜像,以后在同一个事务中再去读取相同记录的数据时,它都可以从快照里面返回这个数据,不管外部怎么样对它操作,在多次读取的时候都不会受到影响。 
幻读:一个事务读取到另一事务已提交的insert数据。

(转)使用Spring注解方式管理事务与传播行为详解的更多相关文章

  1. Spring注解驱动开发01(组件扫描使用详解)

    使用Spring注解代替XML的方式 以前都是通过xml配bean的方式来完成bean对象放入ioc容器,即使通过@Aotuwire自动装配bean,还是要创建一个xml文件,进行包扫描,显得过于繁琐 ...

  2. spring学习笔记---数据库事务并发与锁详解

    多事务运行并发问题 在实际应用中,往往是一台(或多台)服务器向无数客户程序提供服务,当服务器查询数据库获取数据时,如果没有采用必要的隔离机制,可能会存在数据库事务的并发问题,下面是一些常见的并发问题分 ...

  3. 多事务运行并发问题spring学习笔记——数据库事务并发与锁详解

    多事务运行并发问题 在实际应用中,往往是一台(或多台)服务器向无数客户程序提供服务,当服务器查询数据库获取数据时,如果没有采用必要的隔离机制,可能会存在数据库事务的并发问题,下面是一些常见的并发问题分 ...

  4. spring+springMVC+Mybatis 中使用@Transcational方式管理事务的配置方法

    springMVC 中,事务通常都在service层控制,当然controller层也可以用事务,只要配置配对,但通常不建议直接在controller层配事务,controller的作用是管理参数以及 ...

  5. spring+springMVC中使用@Transcational方式管理事务的必须要配的东西。

    spring中管理事务的配置方式除了@Transcational还有使用aop等,本文介绍@Transcational方式. 关于这两种方式的选择: aop方式适合需要支持事务的方法或类较多,且方法和 ...

  6. spring 中常用的两种事务配置方式以及事务的传播性、隔离级别

    一.注解式事务 1.注解式事务在平时的开发中使用的挺多,工作的两个公司中看到很多项目使用了这种方式,下面看看具体的配置demo. 2.事务配置实例 (1).spring+mybatis 事务配置 &l ...

  7. spring 事务配置方式以及事务的传播性、隔离级别

    在前面的文章中总结了spring事务的5中配置方式,但是很多方式都不用而且当时的配置使用的所有参数都是默认的参数,这篇文章就看常用的两种事务配置方式并信息配置事务的传播性.隔离级别.以及超时等问题,废 ...

  8. spring注解方式在一个普通的java类里面注入dao

    spring注解方式在一个普通的java类里面注入dao @Repositorypublic class BaseDaoImpl implements BaseDao {这是我的dao如果在servi ...

  9. SQL Server 表的管理_关于事务的处理的详解(案例代码)

    SQL Server 表的管理_关于事务的处理的详解(案例代码) 一.SQL 事务 1.1SQL 事务 ●事务是在数据库上按照一定的逻辑顺序执行的任务序列,既可以由用户手动执行,也可以由某种数据库程序 ...

随机推荐

  1. 【CQ18阶梯赛第8场】题解

    [A:HDU2032 杨辉三角]: 简单的递推,或者是基础的DP: 但是只有杨润东一个人1A,整体准确率只有8/37,具体原因不详. 经验:提交前一定要试一下比较特殊的数据或者最大的数据.其次,为了保 ...

  2. bzoj 2151 种树 —— 思路+链表

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2151 先都放进堆里取最大的,但选了一个就不能选它两边的,所以可能不是最优,要有“反悔”的措施 ...

  3. You can't specify target table 'xxx' for update in FROM clause

    1.执行sql语句报上面的错误: DELETE FROM db_student WHERE RowGuid IN ( SELECT RowGuid FROM db_student WHERE age ...

  4. socket入门教程

    Server.cs   服务端程序 using System; using System.Collections.Generic; using System.ComponentModel; using ...

  5. mysql数据库基本操作(五)

    表纪录之查(单表查询)2 上一篇讲了4类查询的方式,现在接着上一篇继续看还有哪些方式. group by 分组查询 顾名思义,分组查询要分组,因为mysql5.7之后默认不支持group by语句,需 ...

  6. 51nod 1003【数学】

    思路: 2和5能构成0,然后就是看2和5因子组成个数,然而我们知道,1-n中2的因子数肯定>5的,所以我们只要求一下1-n中5的因子个数就好了... #include <stdio.h&g ...

  7. POJ3264【线段树】

    求区间最值-(基础,继续) code---.. #include<cstdio> #include<iostream> #include<string.h> #in ...

  8. HDU 1230饭前开胃菜

    题意不讲了.. 没思路,上去就是干.... 两个所谓要加的数直接存到数组,开一个标记的数组,然后直接加,乱搞一波,就好了. 细心一点. #include<iostream> #includ ...

  9. hdoj1116【欧拉回路】

    题意: 应该是求一个路径让所有的单词能够首尾连起来,不需要头和尾的单词也连起来...应该很容易懂吧...我这里讲的好烂.. 思路: 从欧拉回路的定义引申过来. 1. 连通. 2. 入度和出度问题. 问 ...

  10. CF1060E Sergey and Subway(点分治)

    给出一颗$N$个节点的树,现在我们**在原图中**每个不直接连边但是中间只间隔一个点的两个点之间连一条边. 比如**在原图中**$u$与$v$连边,$v$与$w$连边,但是$u$与$w$不连边,这时候 ...