zoukankan      html  css  js  c++  java
  • 9.Spring系列之事务

    一、事务简介


    事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性;

    事务就是一系列的动作, 它们被当做一个单独的工作单元,这些动作要么全部完成,要么全部不起作用;

    事务的四个关键属性(ACID):

    • 原子性(atomicity):事务是一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成要么完全不起作用;
    • 一致性(consistency):一旦所有事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中;
    • 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏;
    • 持久性(durability):一旦事务完成, 无论发生什么系统错误,它的结果都不应该受到影响,通常情况下,事务的结果被写到持久化存储器中

    二、原始使用事务的问题


     在我们学习JDBC的时候,最常见的实现业务的写法是:

    Connection conn = null;
    try {
      conn = dataSource.getConnection();
      conn.setAutoCommit(false);
      /** 执行业务方法 **/
      conn.commit();
    } catch (Exception e) {
      if(conn != null) {
        conn.rollback();
      }
    } finally {
      if(conn != null) {
        conn.close();
      }
    }

    可以看出:

    1.我们在写业务方法时,必须为不同的方法重写类似代码;

    2.刚刚也说了,这是学JDBC时候经常用的写法来达到控制事务,那么如果我们还其它数据库存取方案,代码则需要大量做出修改。

    三、Spring中的事务


    作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层,而应用程序开发人员不必了解底层的事务管理 API,就可以使用 Spring 的事务管理机制;

    Spring 既支持编程式事务管理,也支持声明式的事务管理;

    编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码;

    声明式事务管理:大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,事务管理作为一种横切关注点,可以通过 AOP 方法模块化,Spring 通过 Spring AOP 框架支持声明式事务管理;

    注意:声明式事务时基于AOP框架支持,所以我们大概能猜测得到,在执行业务方法之前,前置通知开启事务,执行业务方法之后后置通知提交事务,如果业务方法执行异常了那么异常通知回滚事务。

    四、Spring中的事务管理器


    Spring 从不同的事务管理 API 中抽象了一整套的事务机制,开发人员不必了解底层的事务 API,就可以利用这些事务机制,有了这些事务机制, 事务管理代码就能独立于特定的事务技术了;

    Spring 的核心事务管理抽象是org.springframework.transaction Interface PlateformTransactionManager,它为事务管理封装了一组独立于技术的方法,无论使用 Spring 的哪种事务管理策略(编程式或声明式),事务管理器都是必须的 !

    其不同实现为:

    org.springframework.jdbc.datasource Class DataSourceTransaction
    在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取
    org.springframework.transaction.jta Class JtaTransactionManager
    在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
    org.springframework.orm.hibernate3 Class HibernateTransactionManager
    用 Hibernate 框架存取数据库

    注意:事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中

    五、使用注解的方式声明式的管理事务

    1.为方法添加 @Transactional 注解

    2.根据 Spring AOP 基于代理机制,只能标注公有方法

    3.在方法或者类级别上添加 @Transactional 注解,如果添加到类上,这个类中的所有公共方法都会被定义成支持事务处理

    4.配置如下:

    <!--  配置 C3P0 数据源 --> 
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="user" value="${jdbc.user}" /> 
      <property name="password" value="${jdbc.password}" /> 
      <property name="jdbcUrl" value="${jdbc.jdbcUrl}" /> 
      <property name="driverClass" value="${jdbc.driverClass}" /> 
      <property name="initialPoolSize" value="${jdbc.initPoolSize}" /> 
      <property name="maxPoolSize" value="${jdbc.maxPoolSize}" /> 
    </bean>
    <!--  配置事务管理器 --> 
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" /> </bean> <!-- 启用事务注解 --> <tx:annotation-driven transaction-manager="transactionManager" />

    注意:注解声明式事务是最常用的方式,建议使用注解声明式来控制事务,其它事务不再罗列。

    六、事务的传播行为


    当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播.。比如方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行;

    以下是Spring 支持的事务传播行为:

    REQUIRED:调用方有事务在运行,被调用方的事务方法就在调用方的事务方法中运行;如果调用方没有事务,则新建事务在本身事务中运行;
    REQUIRED_NEW:当前方法有事务在运行,被调用方的事务方法在它自己的事务内运行,调用方的事务方法被挂起,待被调用方的事务方法执行结束后继续;
    SUPPORTS:支持当前事务,假设当前没有事务,就以非事务方式运行;
    NOT_SUPPORTS:以非事务方式运行操作,假设调用方有事务在运行,就把当前事务挂起;
    MANDATORY:调用方有事务在运行,被调用方的事务方法就在调用方的事务方法中运行;如果调用方没有事务,就抛出异常;
    NEVER:以非事务方式运行,假设调用方有事务在运行,则抛出异常;
    NESTED:如果调用方有事务方法在运行,被调用方应该在调用方事务的嵌套事务内运行,否则,就启动一个新的事务并在自己的事务内运行

    注意:常用的是REQUIRED和REQUIRED_NEW,以下详细说明这两种事务传播行为 !

    --->REQUIRED传播行为:以网上搜来的图为例来说明

    说明:

    1.在checkout这个方法内运行着事务,分别是TX1开始事务,TX1结束事务;

    2.在本事务内,存在着被调用方,被调用方也运行着事务方法,从图中看,这可能是循环调用存在事务的purchase方法;

    3.我们假设就是循环调用存在事务的purchase方法,那么以伪代码来体现:

    @Transactional
    public void checkout() {
      // 执行批量购买
       for(int i=0;i<10;i++) {
         purchase(i);
       }
    }
    
    @Transactional
    public void purchase(i) {
      System.out.println("购买了i="+i);
    }

    4.进入checkout方法时开启事务,开始执行for循环时,purchase发现调用方方法checkout存在事务,则把自己也交给checkout事务管理;

    5.一旦for循环执行purchase其中一个方法抛出异常,那么之前purchase执行成功都不算数,全部被回滚,这就体现了要么事务一起成功,要么一起失败。

    说明:默认的事务时REQUIRED,即所有被调用的方法都是用调用方的事务,不再使用自身的事务。

    效果:如果被调用方的方法一旦出现异常,那么所有被调用方执行的结果都被回滚。

    --->REQUIRED_NEW传播行为:以网上搜来的图为例来说明

    另一种常见的传播行为是 REQUIRES_NEW,它表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起它

    说明:

    1.在checkout这个方法内运行着事务,分别是TX1开始事务,TX1结束事务;

    2.在本事务内,存在着被调用方,被调用方也运行着事务方法,从图中看,这可能是循环调用存在事务的purchase方法;

    3.我们假设就是循环调用存在事务的purchase方法,那么以伪代码来体现:

    @Transactional
    public void checkout() {
      // 执行批量购买
       for(int i=0;i<10;i++) {
         purchase(i);
       }
    }
    
    @Transactional
    public void purchase(i) {
      System.out.println("购买了i="+i);
    }

    4.进入checkout方法时开启事务,开始执行for循环时,purchase发现调用方方法checkout存在事务,则把checkout方法的事务挂起,开始自己的事务

    5.假设当i=5的时候,执行抛出了异常,那么从i=0到i=4的这5次执行并不会被回滚,因为它是单独的一个事务,而i=5这次执行被回滚了。

    效果:如果被调用方的方法出现异常,那么所有被调用方执行的成功的结果不会被回滚。

    七、高并发中的事务问题


    1.更新丢失

    两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的

    第一类更新丢失(回滚覆盖):撤消一个事务时,在该事务内的写操作要回滚,把其它已提交的事务写入的数据覆盖了;

    第二类更新丢失(提交覆盖):提交一个事务时,写操作依赖于事务内读到的数据,读发生在其他事务提交前,写发生在其他事务提交后,把其他已提交的事务写入的数据覆盖了,这是不可重复读的特例。

    2.脏读

    一个事务看到了另一个事务未提交的更新数据;当事务读取尚未提交的数据时,就会发生这种情况。

    3.不可重复读

    一个事务中两次读同一行数据,可是这两次读到的数据不一样。

    4.幻读

    一个事务中两次查询,但第二次查询比第一次查询多了或少了几行或几列数据。

    八、事务的隔离级别


    从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题(就如以上问题)。然而,那样会对性能产生极大的影响,因为事务必须按顺序运行;

    在实际开发中,为了提升性能, 事务会以较低的隔离级别运行;

    Spring支持的事务隔离级别:

    待续...

  • 相关阅读:
    【转】正确设置php-fpm子进程用户,提高网站安全性防挂马
    Linux修改SSH端口,并禁止Root远程登陆
    [转]PHP5 session 详解
    【转】PHP调试开发工具你认识多少?
    [转]浅谈php web安全
    【笔记】InnoDB内存分配
    【转】推荐介绍几款小巧的Web Server程序
    【转】定时自动启动任务crontab命令用法
    Tornado笔记
    Python笔记:open函数和with临时运行环境(文件操作)
  • 原文地址:https://www.cnblogs.com/Json1208/p/8763367.html
Copyright © 2011-2022 走看看