Spring系列--声明式事务管理

Spring事务管理概述

Spring的事务管理简化了传统的事务管理流程,并且在一定程序上减少了开发者的工作量。

事务管理的核心接口

在Spring的所有JAR包中包含一个名为Spring-tx-5.0.2.RELEASE的JAR包,该包就是Spring提供的用于事务管理的依赖包。在该JAR包中的org.Springframework.transaction包中有3个接口文件: PlatformTransactionManager、TransactionDefinition和TransactionStatus.

PlatformTransactionManager

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

  1. TransactionStatusgetTransaction(TransactionDefinition definition):用于获取事务状态信息。该方法会根据TransactionDefinition参数返回一个TransactionStatus对象。TransactionStatus对象表示一个事务,被关联在当前执行的线程上。
  2. void commit(TransactionStatus status):用于提交事务。
  3. void rollback(TransactionStatus status): 用于回滚事务。

PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,它只需要事务管理提供上面的3个方法,但具体如何管理事务则由它的实现类来完成。

PlatformTransactionManager接口有许多不同的实现类,常用的几个实现类如下:

  1. org.springframework.jdbc.datasource.DataSourceTransactionManager:用于配置JDBC数据源的事务管理器。
  2. org.springframework.orm.Hibernate4.HibernateTransactionManager: 用于配置Hibernate的事务管理器
  3. org.springframework.transaction.jta.JtaTransactionManager:用于配置全局事务管理器。
TransactionDefinition

TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法,具体如下:

  1. String getName(): 获取事务对象名称
  2. int getlsolationLeve(): 获取事务的隔离级别。
  3. int getPropagationBehavior(): 获取事务的传播行为。
  4. int setTimeout(): 获取事务的超时时间。
  5. iboolean isReadOnly: 获取事务是否只读。

事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种,具体如下表;
Spring系列--声明式事务管理_第1张图片
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。通常情况下,数据的查询不会影响原数据的改变,不需要进行事务管理,而对于数据的插入、更新和删除操作,必须进行事务管理,如果没有指定事务的传播行为,Spring默认传播行为是REQUIRED。

TransactionStatus

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

  1. void flush(): 刷新事务。
  2. boolean hasSavepoint(): 获取是否存在保存点
  3. boolean isCompleted(): 获取事务是否完成
  4. boolean isNewTransaction(): 获取是否是新事物
  5. boolean isRollbackOnly(): 获取是否回滚
  6. void setRollbackOnly(): 设置事务回滚。

事务管理的方式

Spring中的事务管理分为两种方式: 一种是传统的编程序事务管理;另一种是声明式事务管理

  • 编程序事务管理: 通过编写代码实现的事务管理,包含定义事务的开始、正常执行后的事务提交和异常时的事务回滚。
  • 声明式事务管理: 通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个"切面"代码单独编写,然后通过AOP技术将事务管理的"切面"代码植入业务目标类中。

声明式事务管理最大的优点在于开发者无须通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务规则应用到业务逻辑中。

声明式事务管理

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

基于XML方式的声明式事务

基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。
Spring2.0以后,提供了tx命名空间来配置事务,tx命名空间下提供了< tx:advice >元素来配置事务的通知(增强处理)。当使用< tx:advice >元素配置了事务的增强处理后,就可以通过编写AOP配置让Spring自动对目标生成代理。

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

Spring系列--声明式事务管理_第2张图片

代码实现

1.导入下列相关的JAR包
Spring系列--声明式事务管理_第3张图片
前面我们Maven已经导入了所有的JAR包,这里就不再叙说了。

2.在SQLyong中,修改sprint_text的数据表,增加字段jf(积分),初始积分为1000
Spring系列--声明式事务管理_第4张图片
3. 编写User类,添加对应的getter()和setter()方法

package com.ssm.jdbc;

//User实例类
public class User {
    private  Integer id; //用户id
    private  String username; //用户名
    private  String password;//密码
    private  Integer jf;

    public Integer getJf() {
        return jf;
    }

    public void setJf(Integer jf) {
        this.jf = jf;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

4.UserDao接口类,创建一个赠送积分的方法transfer(),代码如下:

package com.ssm.jdbc;

import java.util.List;

public interface UserDao {
    //赠送积分
    public void transfer(String outUser,String inUser,Integer jf);
}

5.在UserDao的实现类UserDaoImpl中实现transfer()方法,代码如下:

  //赠送积分
    @Override
    public void transfer(String outUser, String inUser, Integer jf) {
        String sql = "update sprint_text set jf=jf+? where username=?";
        jdbcTemplate.update(sql,jf,inUser);

        //模拟系统运行时的突发性问题
        int i = 1/0;
        String sql1 = "update sprint_text set jf=jf-? where username=?";
        jdbcTemplate.update(sql1,jf,outUser);
    }

在上述代码中,使用了两个update()方法对user表中的数据执行接收积分和赠送积分的更新操作。在两个操作之间添加了一行代码"int i = 1/0";来模拟系统运行突发性问题。如果没有事务控制,那么在transfer()方法执行后,接收积分用户的积分会增加,而赠送积分的用户的积分会因为系统出现问题而不变,这显然是有 问题的。如果增加了事务控制,那么在transfer()方法操作执行后,接收积分用户的积分和赠送积分用户的积分在问题出现前后都应该保持不变。

4.编写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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"

        xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.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/tx/spring-tx.xsd">


    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <!--连接数据库的url-->
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC"/>
        <!--连接数据库的用户名-->
        <property name="username" value="root"/>
        <!--连接数据库的密码-->
        <property name="password" value="1424025155abc"/>
    </bean>

    <!--2.配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>



    <!--3.定义id为userDao的Bean-->
    <bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
        <!--将jdbcTemplate注入userDao实例中-->
        <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>
            <tx:method name="*" propagation="REQUIRED"
                       isolation="DEFAULT" read-only="false"/>
        </tx:attributes>
    </tx:advice>

    <!--6.编写Aop 让spring自动对目标生成代码,需要使用AspectJ的表达式-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="txPointCut" expression="execution(* com.ssm.jdbc.*.*(..))"/>
        <!--切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

在上述代码,定义了id为transactionManager的事务管理器,接下来通过编写通知来声明事务,最后通过声明AOP的方式让Spring自动生成代理。

5.编写测试方法TransactionTest(),代码如下:

 public  static  void TransactionTest(){
        //1.初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.获取userDao实例
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        //赠送积分
        userDao.transfer("小鑫是仙女","小鑫好看錒",100);
        System.out.println("赠送积分成功! ");
    }

上述代码中,调用了实例中的赠送积分方法,由"小鑫是仙女"向"小鑫好看錒"转让100积分,那么在整个赠送积分方法执行完毕后,两个的积分应该都是原来的值。

运行程序后,控制台报出了/ by zero错误
Spring系列--声明式事务管理_第5张图片
打开SQLyong,发送两个人的积分并没有变化,说明Spring中的事务管理配置已经生效了。
Spring系列--声明式事务管理_第6张图片

基于Annotation方式的声明式事务

基于Annotaion(注解)的方式使用非常简单,两步就可以搞定
1.在Spring容器中注册事务注解驱动,代码如下

<tx:annotation-driven transaction-manager="transactionManager"/>

2.在需要使用事务的Spring Bean类或者Bean类的方法上添加注解@Transactional。如果将注解添加在Bean类上,就表示事务的设置对整个Bean类的所有方法都起作用;如果将注解添加在Bean类中的某个方法上,就表示事务的设置只对该方法有效。

使用@Transactional注解时,可以通过其参数配置事务详情。如下表所示
Spring系列--声明式事务管理_第7张图片

代码实现

1.在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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"

        xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.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/tx/spring-tx.xsd">


    <!--1.配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--数据库驱动-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <!--连接数据库的url-->
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=UTC"/>
        <!--连接数据库的用户名-->
        <property name="username" value="root"/>
        <!--连接数据库的密码-->
        <property name="password" value="1424025155abc"/>
    </bean>

    <!--2.配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>



    <!--3.定义id为userDao的Bean-->
    <bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
        <!--将jdbcTemplate注入userDao实例中-->
        <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>

与基于XML方式的配置文件相比,通过注册事务管理器驱动替换了编写通知和编写aop的配置,这样大大减少了配置文件中的代码量。

注意:
如果案例中使用了注解式开发,就需要在配置文件中开启注解处理器,指定扫描哪些包下的注解。这里没有开启注解处理器是因为在配置文件中已经配置了UserDaoImpl类的Bean,而@Transactional注解就配置在该Bean类中,所以可以直接生效。

注解处理器开启代码如下

 <context:component-scan base-package="com.ssm.service">

在配置文件添加了这行代码,说明com.ssm.service包下所有通过注解声明的Bean类,都会被添加到Spring容器中。

2.在UserDaoImpl类的transfer()方法上添加事务注解。代码如下:

 //赠送积分
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
    @Override
    public void transfer(String outUser, String inUser, Integer jf) {
        String sql = "update sprint_text set jf=jf+? where username=?";
        jdbcTemplate.update(sql,jf,inUser);

        //模拟系统运行时的突发性问题
        int i = 1/0;
        String sql1 = "update sprint_text set jf=jf-? where username=?";
        jdbcTemplate.update(sql1,jf,outUser);
    }

注意
在实际开发中,事务的配置信息通常是在Spring的配置文件中完成的,而在业务层类上只需使用@Transactional注解即可,不需要配置@Transactional注解的属性。

3.编写测试方法,代码如下

 public  static  void TransactionTest(){
        //1.初始化Spring容器,加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.获取userDao实例
        UserDao userDao = (UserDao) applicationContext.getBean("userDao");
        //赠送积分
        userDao.transfer("小鑫是仙女","小鑫好看錒",100);
        System.out.println("赠送积分成功! ");
    }

执行的效果和基于XML的声明式事务一致。

你可能感兴趣的:(Spring)