Spring的事务管理

Posted zhilili

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring的事务管理相关的知识,希望对你有一定的参考价值。

什么是事务?

  数据库事务是指作为单个逻辑单元存在的一系列操作,要么完全执行,要么完全不执行。

事务的几个属性:原子性,一致性,隔离性,持久性

  原子性:事务是最小的执行单元,不允许分割,事务的原子操作确保动作要完全完成,要么就是完全不起作用。

  一致性:执行事务的前后,数据要保持一致;

  隔离性:并发访问数据库,一个用户的事务不被其他的事务所干预,数据库是独立的。

  持久性:一个事务被提交后,他对数据库的改变是持久的;

事务管理?

  所谓的事务管理是指 "按照给定的事务规则来执行提交或者回滚操作";

Spring中的事务管理

  在Spring的所有包中,有一个名为spring-tx-xxx的Jar包,该包就是Spring所提供的事务管理的依赖包,在该包的org.springframework.transaction包中,含有3个接口,该三个接口就是PlatformTransactionManager,TransactionDefinition和TransactionStatus,这三个核心API的关系就是:PlatformTransactionManager是根据TransactionDefinition来进行事务管理,在管理的过程中事务存在多个状态信息,每个状态信息是通过TransactionStatus来表示的。

  在项目中,Spring是将XML中的配置的事务详细信息封装到TransactionDefinition对象中,然后通过事务管理器PlatformTransactionManager的getTransaction(TransactionDefinition ddfinition)来获得事务的状态TransactionStatus

1.PlatformTransactionManager

  PlatformTransactionManager接口是Spring提供的平台事务管理器,主要是用于管理事务。接口中提供了三个事务操作的方法,如下:

  TransactionStatus getTransaction(TranscationDefinition definition):用于获取事务的状态信息;

  void commit(TransactionStatus status):用于提交事务。

  void rollback(TransactionStatus status):用于回滚事务。

  在第一个方法中,返回的TransactionStatus对象,是表示一个事务,该事务会被关联在当前执行的线程上。

  PlatformTransactionManager接口只是代表着事务管理的接口,它并不知道底层实现,它只需要以上三个方法来管理事务,但是如何具体的管理事务是由它的实现类来实现,常见的几个实现类如下:

  org.springframework.orm.hubernate4.HibernateTransactionManager:用于配置Hibernate的事务管理器。

  org.springframework.jdbc.datasource.DataSourceTransactionManager:用于配置JDBC数据源的事务管理器。

  org.springframework.transaction.jta.JtaTransactionManager:用于配置全局事务管理器。

  当底层采用不同的事务持久层技术时,就可以使用不同的PlatformTransactionManager的实现类即可。

2.TransactionDefinition 

  TransactioDefinition接口是事务定义的对象,该对象中定义了一系列基本的事务属性(可以理解为事务的一些基本配置,事务的属性:隔离级别,传播行为,回滚规则,是否只读,事务超时),并提供了获取事务相关的信息的方法,具体如下:

  ? string getName():用于获取事务对象的名称;

   ? int getlsolationLevel():获取事务的隔离级别,事务管理器是根据它来控制另外一个事务可以看到本事务内的哪些数据。

  ? int getPropagationBehavior():获取事务的传播行为。

  ? int getTimeout():获取事务的超时时间,既事务必须在多少秒内完成;

  ? Boolean isReadOnly():获取事务是否是只读的;

  ??事务的隔离级别:既定义了一个事务可能受到其他事务影响的程度,TransactionDefinition接口定义了5个表示隔离级别的常量

   并发:多个用户对同一数据进行操作

  (1)脏读:当一个事务正在访问数据库时并且修改数据,这个数据还未提交到数据库中时,这时,另外一个数据也访问了这个数据,因为这个数据是还没有的提交的数据,那么另外一个数据读取到的是"脏数据",依照"脏数据"所做的操作可能是不正确的。

  (2)丢失操作:指在一个事务读取一个数据时,另一个事务也访问了这个数据,那么第一个事务中修改了这个数据,第二个事务中也修改了这个数据,这样第一个修改结果就被丢失了,因此称为丢失操作。例如:事务1 读取到的事务数据A=20,事务2读取到的数据也是20,第一个事务修改了数据A=A-1,第二个事务也修改了A=A-1,那么A=19,事务1的修改结果就被丢失了。

  (3)不可重复读:指在一个事务内多次读取同一个数据,在这个事务还没有结束时,另一个事务也访问该数据,在第一个事务还没有结束时,第二个事务修改导致了第一个事务两次读的数据不一样,这就发生了这个事务读取的两次结果不一样,因此称为不可重复读。

  (4)与不可重读类似,但是它发生在一个事务读取了几行数据后,接着另外一个事务并发插入了一些数据,在随后的查询里,第一个事务就发现多了一些原本不存在的记录,就像是发生了幻觉一样,所以就称为“换读”

  不可重复读的重点在于修改,幻读的重点在于新增或者删除。

  ??事务的超时属性(一个事务允许执行的最长时间)

   如果一个事务超过这个时间没有完成,就自动执行回滚。

  ??事务的只读属性

   事务的只读属性是指对事务性资源(指哪些被事务管理的资源)只读或者读写操作,

  ??回滚规则

    定义了哪些异常会导致事务回滚,而哪些不会,默认情况下,事务只有遇到运行期异常才会回滚,而遇到检查型异常时不会回滚,也可以自定义遇到哪些异常会回滚。

事务的传播行为:

什么是事务的传播行为?  

  我们都知道事务的概念,那么事务的传播特性是什么呢?(此处着重介绍传播特性的概念,关于传播特性的相关配置就不介绍了,可以查看spring的官方文档) 

在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。 
  以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。 

  默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下 如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承 这样事务才会回滚!

  在事务管理的过程中,传播行为可以控制是否需要创建事务,以及如何创建事务,通常情况下,数据的查询时不会影响数据的改变,所以不需要进行事务的管理,但是对于数据的插入,修改,删除,必须进行事务的管理。如果没有指定事务的传播行为,就默认是REQUIRED。

  事务的传播行为的种类:

属性名称 描叙
  PROPAGETION_REQUIRED REQUIRED

表示当前事务必须存在一个事务中,如果当前事务存在在事务中,就直接使用该事务,如果没有处在事务中,就开启一个新的事务,

比如说,ServiceB.methodB的事务级别为PROPAGETION_REQUIRED,那么执行ServiceA.methodA时侯,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在事务里了,就不再起新的事务,如果在ServciceA.methodA运行的时候发现不在事务里,他就会为自己分配一个事务,这样无论在A里面还是在B里面,无论任何地方出现错误,都会回滚。

PROPAGETION_SUPPORTS SUPPORTS 如果在事务里,就以事务的形式运行,如果不在事务里,就以非事务的形式处理。
PROPAGETION_MANDATORY MANDATORY 调用该方法的线程必须在一个事务里,既必须在一个事务里运行,也就是说方法只能被一个父事务调用,否则,就抛出异常。
PROPAGETION_REQUIRES_NEW REQUIRES_NEW

要求方法在新的事务里运行,既如果当前方法在一个事务里,就停止当前事务,开启一个新的事务执行当前的方法,如果方法不在事务里,就开启一个新的事务来执行当前方法。

 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

 PROPAGATION_NOT_SUPPORTED  NOT_SUPPORTED 

不支持事务,既总以非事务的状态执行,

比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

 PROPAGATION_NEVER 

NEVER

不支持当前事务,如果调用该方法的线程处于事务环境中,将抛出异常
PROPAGATION_NESTED  NESTE 理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

 

3.TransactionStatus

  TransactionStatus接口是事务的状态,它描叙了某一时间点上事务的状态信息,该接口中包含了6个方法,具体如下:

  void flush():刷新事务。

  boolean hasSavepoint():获取是否存在保存点。

  boolean isCompleted():获取事务是否完成。

  Boolean isNewTransaction():获取是否是新事物。

  boolean isRollbackOnly():获取是否回滚。

  boolean setRollbackOnly():设置事务回滚。

 

事务管理的方式:一种是声明式的事务管理,一种是传统的编程式事务管理。

  编程式的事务管理:是通过编写代码实现的事务管理,包括定义事务的开始,正常执行后的事务提交和异常时是事务回滚。

  声明式的事务管理:是通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个切面代码单独编写,然后通过AOP技术将事务管理的“切面”代码编织到业务目标类中。

声明式的事务管理:

  Spring的声明式的事务管理可以通过两种方法来实现:一种是基于XML的方式,一种是基于Annotation的方法。

一,基于XML的事务管理 

技术图片

 

 

  基于XML方式的声明式事务管理,是通过在配置文件中配置事务规则的相关声明来实现的,在Spring2.0以后,提供了tx命名空间来配置事务管理,tx命名空间下提供了<tx:advice>元素来配置事务的通知(增强处理). 

  配置<tx:advice>元素时,通常是需要指定id和transaction-manager属性,id属性是配置文件中唯一的标识,transaction-manager是指定使用什么事务管理器。除此之外,还需要在其中配置一个子元素<tx:attributes>,该子元素可以配置多个子元素<tx:method>来配置执行事务的细节。

  配置<tx:advice>元素的重点是配置<tx:method>元素,

实例:

1.创建一个Spring事务管理项目,将Spring数据库操作中的代码复制到Spring事务管理项目里,在AccountDao接口中添加一个方法:

// 转账
    public void transfer(String outUser,String inUser,Double money);

 

2.在AccountDao接口的实现类AccountDaoImpl类中实现该方法

/**
     *  转账
     *  inUser:收款人
     *  outUser:汇款人
     *  money:收款金额
    */
   public void transfer(String outUser, String inUser, Double money) 
       // 收款时,收款用户的余额=现有余额+所汇金额
      this.jdbcTemplate.update("update account set balance = balance +? "
               + "where username = ?",money, inUser);
      // 模拟系统运行时的突发性问题
       int i = 1/0;
        // 汇款时,汇款用户的余额=现有余额-所汇金额
        this.jdbcTemplate.update("update account set balance = balance-? "
               + "where username = ?",money, outUser);
   

 

3.修改Spring配置文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 1.配置数据源 -->
    <bean id="dataSource" 
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!--连接数据库的url -->
        <property name="url" value="jdbc:mysql://localhost/spring" />
        <!--连接数据库的用户名 -->
        <property name="username" value="root" />
        <!--连接数据库的密码 -->
        <property name="password" value="root" />
   </bean>
   <!-- 2.配置JDBC模板 -->
   <bean id="jdbcTemplate" 
          class="org.springframework.jdbc.core.JdbcTemplate">
         <!-- 默认必须使用数据源 -->
         <property name="dataSource" ref="dataSource" />
   </bean>
   <!--3.定义id为accountDao的Bean -->
   <bean id="accountDao" class="com.itheima.jdbc.AccountDaoImpl">
         <!-- 将jdbcTemplate注入到AccountDao实例中 -->
         <property name="jdbcTemplate" ref="jdbcTemplate" />
   </bean>    
   <!-- 4.事务管理器,依赖于数据源 -->
   <bean id="transactionManager" class=
   "org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
   </bean>    
   <!-- 5.编写通知:对事务进行增强(通知),需要编写对切入点和具体执行事务细节 -->
   <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- name:*表示任意方法名称 -->
            <tx:method name="*" propagation="REQUIRED" 
                           isolation="DEFAULT" read-only="false" />
        </tx:attributes>
    </tx:advice>
    <!-- 6.编写aop,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
    <aop:config>
        <!-- 切入点 -->
        <aop:pointcut expression="execution(* com.itheima.jdbc.*.*(..))"
            id="txPointCut" />
        <!-- 切面:将切入点与通知整合 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
    </aop:config>
</beans>

 

4.创建一个测试类TransactionTest.java,该类用来测试事务管理的方法

package com.itheima.jdbc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import 
org.springframework.context.support.ClassPathXmlApplicationContext;
//测试类
public class TransactionTest 
    @Test
    public void xmlTest()
        ApplicationContext applicationContext = 
           new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取AccountDao实例
        AccountDao accountDao = 
            (AccountDao)applicationContext.getBean("accountDao");
        // 调用实例中的转账方法
        accountDao.transfer("Jack", "Rose", 100.0);
        // 输出提示信息
        System.out.println("转账成功!");
    

 

 

二.基于注解的事务管理

  基于注解的事务管理使用非常简单,只需要在配置文件中配置事务管理器,再开启事务注解驱动 (不需要再配置事务规则,也不需要配置切面将切入点和事务规则整合起来)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-4.3.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <!-- 1.配置数据源 -->
    <bean id="dataSource" 
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!--连接数据库的url -->
        <property name="url" value="jdbc:mysql://localhost/spring" />
        <!--连接数据库的用户名 -->
        <property name="username" value="root" />
        <!--连接数据库的密码 -->
        <property name="password" value="root" />
    </bean>
    <!-- 2.配置JDBC模板 -->
    <bean id="jdbcTemplate" 
            class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 默认必须使用数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!--3.定义id为accountDao的Bean -->
    <bean id="accountDao" class="com.itheima.jdbc.AccountDaoImpl">
        <!-- 将jdbcTemplate注入到AccountDao实例中 -->
        <property name="jdbcTemplate" ref="jdbcTemplate" />
    </bean>
    <!-- 4.事务管理器,依赖于数据源 -->
    <bean id="transactionManager" class=
     "org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>    
    <!-- 5.注册事务管理器驱动 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

   需要注意的是因为案例中使用了注解开发,则需要在配置文件中开启注解处理器,指定扫描哪些包下的注解,这里没有开启注解是因为事务的注解@Transactional注解配置在了AccountDaoImpl中了,而在配置文件中已经配置了AccountDaoImpl的bean,可以直接生效,(不然的话要配置<context:component-scan base-package="Bean所在的包路劲"/>来扫描注解所在的包)

实例:

  1.把AccountDaoImpl中的转账方法换为如下,添加注解

    @Transactional(propagation = Propagation.REQUIRED, 
            isolation = Isolation.DEFAULT, readOnly = false)
    public void transfer(String outUser, String inUser, Double money) 
        // 收款时,收款用户的余额=现有余额+所汇金额
        this.jdbcTemplate.update("update account set balance = balance +? "
                + "where username = ?",money, inUser);
        // 模拟系统运行时的突发性问题
        int i = 1/0;
        // 汇款时,汇款用户的余额=现有余额-所汇金额
        this.jdbcTemplate.update("update account set balance = balance-? "
                + "where username = ?",money, outUser);
    

 

  2.在测试类中添加测试方法

@Test
    public void annotationTest()
        ApplicationContext applicationContext = 
    new ClassPathXmlApplicationContext("applicationContext-annotation.xml");
        // 获取AccountDao实例
        AccountDao accountDao = 
    (AccountDao)applicationContext.getBean("accountDao");
        // 调用实例中的转账方法
        accountDao.transfer("Jack", "Rose", 100.0);
        // 输出提示信息
        System.out.println("转账成功!");
    

 

  声明式事务管理的两种方法如上了

以上是关于Spring的事务管理的主要内容,如果未能解决你的问题,请参考以下文章

spring怎么进行的事务管理,求指导?

Spring_8-Spring事务管理

什么叫做spring的声明式事务

Spring的学习(Spring事务管理)

Spring的事务管理有几种方式?Spring常用的实物隔离级别是哪几种?

spring 中的事物管理问题