zoukankan      html  css  js  c++  java
  • Spring的学习(四、Spring事务管理)

    Spring事务管理的三个核心接口

    Spring的事务管理是基于AOP实现的,而AOP是以方法为单位的。

    Spring的事务属性分别为传播行为、隔离级别、只读和超时属性。所有这些属性提供了事务应用的方法和描述策略。

    事务管理的三个核心接口:PlatformTransactionManager、TransactionDefinition、TransactionStatus。

    1. PlatformTransactionManager

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

    (1)TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。

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

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

    在项目中通过XML配置事务的详细信息,Spring将这些信息封装到对象TransactionDefinition中,通过事务管理器的getTransaction()方法获得事务的状态TransactionStatus,就可以对事务进行下一步的操作。

    2. TransactionDefinition

    TransactionDefinition接口是事务定义(描述)对象,提供事务相关信息获取的方法,包括5个操作,具体如下:

    (1)String getName():获取事务对象名称。

    (2)int getIsolationLevel():获取事务的隔离级别。

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

    (4)int getTimeout():获取事务的超时时间。

    (5)boolean isReadOnly():获取事务是否只读。

    需要注意:事务传播行为的概念,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。

    在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响原数据的改变,所以不需要进行事务管理,而对于数据的增加、修改、删除等操作,必须进行事务管理,如果没有指定事务的传播行为,默认传播行为是required。

    传播行为的种类
    属性名称 描述
    PROPAGATION_REQUIRED required 支持当前事务。如果A方法已经在事务中,B将直接使用。如果没有将创建新事务。
    PROPAGATION_SUPPORTS supports 支持当前事务。如果A方法已经在事务中,B将直接使用。如果没有将以非事务状态执行。
    PROPAGATION_MANDATORY mandatory 支持当前事务。如果A方法没有事务,将抛异常。
    PROPAGATION_REQUIRES_NEW requires_new 将创建新的事务,如果A方法已经在事务中,将A事务挂起。
    PROPAGATION_NOT_SUPPORTED not_supported 不支持当前事务,总是以非事务状态执行。如果A方法已经在事务中,将挂起。
    PROPAGATION_NEVER never 不支持当前事务,如果A方法在事务中,将抛异常。
    PROPAGATION_NESTED nested 嵌套事务,底层将使用Savepoint形成嵌套事务。

    3. TransactionStatus

    TransactionStatus接口是事务的状态,描述了某一时间点上事务的状态信息,包含6个操作,具体如下:

    (1)void flush():刷新事务。

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

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

    (4)boolean isNewTransaction():获取是否是新事务。

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

    (6)void setRollbackOnly():设置事务回滚。

     

    TransactionProxyFactoryBean实现声明式事务管理

    Spring的事务管理分为两种方式,声明式事务管理和编程式事务管理。

    编程式事务管理

    使用事务模板TransactionTemplate手动地管理事务,在实际开发中一般不使用,这里了解即可。

    声明式事务管理

    是Spring最原始的事务管理方式,我们需要在配置文件中定义数据源和事务管理器,然后把事务管理器注入到TransactionProxyFactoryBean中,设置目标类和事务的相关属性,使用TransactionProxyFactoryBean生成代理,它的优势在于代码中无须关注事务逻辑,而是交给Spring容器进行事务控制。

    数据库:表名:acount 字段:id name money 存两组值:1,jack,1000/2,rose,1000。

    c3p0-db.properties

    jdbc.driverClass=com.mysql.jdbc.Driver
    jdbc.jdbcUrl=jdbc:mysql:///spring
    jdbc.user=root
    jdbc.password=root

    applicationContext.xml

    <?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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd"
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
            <!--加载properties文件 -->
            <context:property-placeholder location="classpath:c3p0-db.properties"/>
            <!--配置数据源,读取properties文件信息 -->
            <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <property name="driverClass" value="${jdbc.driverClass}"></property>
                <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
                <property name="user" value="${jdbc.user}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </bean>
            <!--配置JDBC模板 -->
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                <property name="jdbcTemplate" ref="dataSource"></property>
            </bean>
            <!--配置dao -->
            <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl">
                <property name="jdbcTemplate" ref="jdbcTemplate"></property>
            </bean>
            <!--配置service -->
            <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl">
                <property name="accountDao" ref="accountDao"></property>
            </bean>
            <!--事务管理器,依赖于数据源 -->
            <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="dataSource"></property>
            </bean>
            <!--生成代理类,让代理管理事务,依赖于事务管理器 -->
            <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                <!--提供事务管理器 -->
                <property name="transactionManager" ref="transactionManager"></property>
                <!--目标类 -->
                <property name="target" ref="accountService"></property>
                <!--提供接口 -->
                <property name="proxyInterfaces" value="cn.tm.service.AccountService"></property>
                <!--事务的详情配置,给TransactionDefinition进行赋值 -->
                <property name="transactionAttributes">
                    <props>
                        <!--key属性用来配置对目标内的哪些方法进行增强,*代表所有方法,如果是save*,表示以save开头的方法 -->
                        <!--text文本按照固定的格式编写事务的详情及TransactionDefinition的内容,例如传播行为、隔离界别等,值之间用逗号隔开 -->
                        <prop key="*">PROPAGATION_REQUIRED,ISOLATION_REPEATABLE_READ</prop>
                    </props>
                </property>
            </bean>
    </beans>

    AcountDao.java+AccountDaoImpl.java

    package cn.tm.dao;
    public interface AccountDao{
        //汇款
        public void out(String outUser,int money);
        //收款
        public void in(String inUser,int money);
    }
    
    package cn.tm.dao.impl;
    public class AccountDaoImpl implements AccountDao{
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
            this.jdbcTemplate = jdbcTemplate;
        }
        //汇款的实现方法
        public void out(String outUser, int money) {
            this.jdbcTemplate.update("update account set money = money-?"+"where name=?",money,outUser);
        }
        //收款的实现方法
        public void in(String inUser, int money) {
            this.jdbcTemplate.update("update account set money = money+?"+"where name=?",money,inUser);
        }
    }

    AccountService.java+AccountServiceImpl.java

    package cn.tm.service;
    public interface AccountService{
        //转账方法
        public void transfer(String outUser,String inUser,int money);
    }
    
    package cn.tm.service.impl;
    public class AccountServiceImpl implements AccountService{
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao){
            this.accountDao = accountDao;
        }
        @Override
        public void transfer(String outUser, String inUser, int money) {
            this.accountDao.out(outUser,money);
            this.accountDao.in(inUser,money);
        }
    }

    测试类

    public class Test{
        public static void main(String[] args) {
            //xmlPath为applicationContext.xml文件的路径
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            AccountService accountService = (AccountService)applicationContext.getBean("accountServiceProxy");
            //从jack的账户转100到rose的账户上
            accountService.transfer("jack","rose",100);
            System.out.println("ok");
        }
    }

    效果:

    jack剩余900元,rose剩余1100元。如果转账的代码有问题,比如出现1/0这种情况,则两人钱仍然是1000元。

    Spring AOP XML方式

    上面的TransactionProxyFactoryBean实现声明式事务管理缺点是配置文件过于臃肿、难以阅读。因此,Spring提供了基于tx/AOP配置的声明式事务管理方式,也是实际开发中最常用的一种方式。其他代码同上,只修改了applicationContext.xml和测试类的代码。

    applicationContext.xml

    <?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"
           xmlns:tx="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
           http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/aop/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
            <!--加载properties文件 -->
            <context:property-placeholder location="classpath:c3p0-db.properties"/>
            <!--配置数据源,读取properties文件信息 -->
            <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <property name="driverClass" value="${jdbc.driverClass}"></property>
                <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
                <property name="user" value="${jdbc.user}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </bean>
            <!--配置JDBC模板 -->
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                <property name="jdbcTemplate" ref="dataSource"></property>
            </bean>
            <!--配置dao -->
            <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl">
                <property name="jdbcTemplate" ref="jdbcTemplate"></property>
            </bean>
            <!--配置service -->
            <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl">
                <property name="accountDao" ref="accountDao"></property>
            </bean>
            <!--事务管理器,依赖于数据源 -->
            <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="dataSource"></property>
            </bean>
            <!--编写通知:对事物进行增强(通知),需要编写对切入点和具体执行事务细节 -->
            <tx:advice id="txAdvice" transaction-manager="txManager">
                <tx:attributes>
                    <!-- tx:method给切入点方法添加事务详情 name:方法名称,*表示任意方法名称,save* 即是以save开头  -->
                    <!-- propagation:设置传播行为  isolation:隔离级别  read-only:是否只读  -->
                    <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
                </tx:attributes>
            </tx:advice>
            <!--aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
            <aop:config>
                <!--切入点 -->
                <aop:pointcut expression="execution(* cn.tm.service.*.*(..))" id="txPointCut"/>
                <!--切面:将切入点与通知整合 -->
                <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
            </aop:config>
    </beans>

    测试类:(getBean内的内容做了修改)

    public class Test{
        public static void main(String[] args) {
            //xmlPath为applicationContext.xml文件的路径
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
            AccountService accountService = (AccountService)applicationContext.getBean("accountService");
            //从jack的账户转100到rose的账户上
            accountService.transfer("jack","rose",100);
            System.out.println("ok");
        }
    }

    Spring AOP Annotation方式

     

    Spring的声明式事务管理还可以通过Annotation注解的方式,这种方式很简单。我们需要做的只有两步:Spring容器中注册驱动,在需要使用事务的业务类或者方法上添加注解@Transactional。

    applicationContext.xml

    <?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"
           xmlns:tx="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/cache"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
           http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/aop/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
            <!--加载properties文件 -->
            <context:property-placeholder location="classpath:c3p0-db.properties"/>
            <!--配置数据源,读取properties文件信息 -->
            <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <property name="driverClass" value="${jdbc.driverClass}"></property>
                <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
                <property name="user" value="${jdbc.user}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </bean>
            <!--配置JDBC模板 -->
            <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
                <property name="jdbcTemplate" ref="dataSource"></property>
            </bean>
            <!--配置dao -->
            <bean id="accountDao" class="cn.tm.dao.impl.AccountDaoImpl">
                <property name="jdbcTemplate" ref="jdbcTemplate"></property>
            </bean>
            <!--配置service -->
            <bean id="accountService" class="cn.tm.dao.impl.AccountServiceImpl">
                <property name="accountDao" ref="accountDao"></property>
            </bean>
            <!--事务管理器 -->
            <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="dataSource"></property>
            </bean>
            <!--注册事务管理驱动 -->
            <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    </beans>

    AccountServiceImpl.java

    package cn.tm.service.impl;
    /*这里添加Transactional注解,并且使用注解的参数配置了事务详情,参数之间用逗号进行分隔*/
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
    public class AccountServiceImpl implements AccountService{
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao){
            this.accountDao = accountDao;
        }
        @Override
        public void transfer(String outUser, String inUser, int money) {
            this.accountDao.out(outUser,money);
            this.accountDao.in(inUser,money);
        }
    }

    Spring的事务传播机制

    Spring事务机制主要包括声明式事务和编程式事务。编程式不常用。

    Spring声明式事务让我们从复杂的事务处理中得到解脱。使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性。事务属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务只读标志组成。我们在进行事务划分时,需要进行事务定义,也就是配置事务的属性。

    spring在TransactionDefinition接口中定义了七个事务传播行为:

    (1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。(默认)

    (2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

    (3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

    (4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

    (5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    (6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

    (7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。

    那么看下面的例子:

    (1)PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中。

    现在有方法A和B,方法B在方法A中被调用,方法A、B上都有事务注解并为propagation_requierd级别,问单独调用方法A和B结果有什么不同?

    如果单独调用B方法,因为没有事务,所以会先开启一个新的事务。

    如果直接调用A方法,环境中没有事务,会开启一个新事务,当调用到方法B的时候,因为环境中已经有事务了,所以方法B就加入到了当前事务,不会新建事务了。

    //事务属性 PROPAGATION_REQUIRED 
    methodA{
        ...
        methodB();
    }
    //事务属性 PROPAGATION_REQUIRED 
    methodB{
        ...
    }

    (2)PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

    情况跟上一个例子类似,也是两个方法,不过区别在于方法B的级别改成了propagation_supports,那么单独调用方法A和B结果有什么不同?

    如果单独调用B方法,方法B是以非事务的方式执行的。

    如果调用方法A,方法B会加入到方法A中,以事务的方式执行。

    //事务属性 PROPAGATION_REQUIRED 
    methodA{
        ...
        methodB();
    }
    //事务属性 PROPAGATION_SUPPORTS 
    methodB{
        ...
    }

    (3)PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

    如果单独调用B方法,因为当前没有一个活动的事务,会抛出throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);异常。

    如果调用方法A,方法B会加入到方法A的事务中,事务的执行。

    //事务属性 PROPAGATION_REQUIRED 
    methodA(){ 
        methodB(); 
    }
     
    //事务属性 PROPAGATION_MANDATORY 
    methodB(){ 
        …… 
    }

    (4)PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。

    如果单独调用B方法,会开启一个新的事务,事务的执行。

    如果调用方法A,当执行到方法B的时候,方法A的事务(事务A)会被挂起,方法B中创建一个事务(事务B),在事务B中执行,那么如果方法B成功处理,执行下面的doSomeThingB方法时,如果报错了,方法B的事务不会受到影响,方法A的代码除了方法B以外,都会回滚。

    //事务属性 PROPAGATION_REQUIRED 
    methodA(){ 
       doSomeThingA(); 
       methodB(); 
       doSomeThingB(); 
    }
     
    //事务属性 PROPAGATION_REQUIRES_NEW 
    methodB(){ 
       …… 
    }

    (5)PROPAGATION_NOT_SUPPORTED  总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)

    (6)PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常;

    (7)PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

    这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而nestedTransactionAllowed属性值默认为false;

    嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

    如果单独调用方法B,会以propagation_requierd方式,即创建一个事务去执行。

    如果调用方法A,如果方法A中代码报错,那么方法B中的事务也会回滚。(嵌套事务)

    //事务属性 PROPAGATION_REQUIRED 
    methodA(){ 
       doSomeThingA(); 
       methodB(); 
       doSomeThingB(); 
    }
     
    //事务属性 PROPAGATION_NESTED 
    methodB(){ 
      …… 
    }

    参考:

    1. 《SSH框架整合实战教程》

    2.  https://www.cnblogs.com/myseries/p/10800430.html

    持续更新!!!

  • 相关阅读:
    【leetcode】1442. Count Triplets That Can Form Two Arrays of Equal XOR
    【leetcode】1441. Build an Array With Stack Operations
    【leetcode】1437. Check If All 1's Are at Least Length K Places Away
    cxCheckCombobox
    修改现有字段默认值
    2018.01.02 exprottoexcel
    Statusbar OwnerDraw
    dxComponentPrinter记录
    单据暂存操作思路整理
    设置模式9(装饰者,责任链,桥接,访问者)
  • 原文地址:https://www.cnblogs.com/flyinghome/p/12628525.html
Copyright © 2011-2022 走看看