Spring框架(编程式事务和声明式事务)

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

Spring知识

  一丶SpringIOC初步认识↓↓↓
第一篇---->初识Spring
  二丶SpringIOC深入↓↓↓
第二篇---->深入SpringIoC容器(一)
第三篇---->深入SpringIoC容器(二)
  三丶装配SpringBean↓↓↓
第四篇---->依赖注入的方式
第五篇---->基于xml装配Bean
第六篇---->基于注解装配Bean
第七篇---->Spring Bean之间的关系
第八篇---->SpringBean的作用域
第九篇---->Spring 加载属性(properties)文件
第十篇---->Spring表达式(SpEL)
第十一篇---->Spring在xml中配置组件扫描
  四丶面向切面编程↓↓↓
第十二篇—>认识SpringAOP及底层原理
第十三篇—>使用@AspectJ注解开发AOP
第十四篇—>使用xml配置开发AOP
  五丶Spring中数据库编程↓↓↓
第十五篇—>数据库编程JdbcTemplate
  六丶Spring事务管理↓↓↓
第十六篇—>Spring事务管理初识
第十七篇—>编程式事务和声明式事务
第十八篇—>事务ACID特性
第十九篇—>事务传播行为
第二十篇—>事务隔离级别

4 编程式事务

  • 编程式事务通过使用代码的方式来管理事务,事务的逻辑将由开发者通过自己的代码来实现,这里需要定义一个事务定义类的接口---TransactionDefinition(关于这个事务定义器后面会详细说),然后使用其实现了–DefaultTransactionDefinition即可.代码如下
      Spring框架(编程式事务和声明式事务)_第1张图片
  • 上面只是提供一个例子,可以了解编程式事务的基本流程,如果想要运行还需要加入mysql连接相关的jar包,以及在数据库中创建好表,提供一下资料(jar包和建表),需要可下载
    &emesp; 链接:https://pan.baidu.com/s/1RikhZ1JMvAvWDqk60oqiYg 提取码:dqy8
  • 从上图中可以看到所有的事务都是由自己进行控制的,由于事务已经交给事务管理器管理,所以JdbcTemplate本身的数据库资源已经有事务管理器管理,当其执行完sql语句后并不会自动提交事务,这时候就需要使用事务管理器的commit方法,回滚事务使用rollback方法
  • 不得不提的是,该方式已经不是主流方式,甚至几乎不用了,所以了解即可,知道有这回事就行了

5 声明式事务

  • 大部分情况下,使用数据库事务时,发生异常则回滚,否则则提交事务,从而保证了数据库数据的一致性.在Spring中将其作为了一种默认约定,如果使用的是声明式事务,当你的业务方法不发生异常(或者发生异常,但是通过配置可以指定发生某异常时也提交事务),Spring就会让事务管理器提交事务,而发生异常(并且该异常不被配置信息允许提交事务),则Spring会让事务管理器回滚事务.
  • 声明式事务可以由xml或者注解@Transactional进行配置

5.1 @Transactional的配置项

  • 首先看一下Transactional源码
      Spring框架(编程式事务和声明式事务)_第2张图片
  • 从源码中可以看出Transactional包含的配置项真的不多,但是非常重要,下面用一个表格详细解析各个配置项的含义
      Spring框架(编程式事务和声明式事务)_第3张图片
  • 这里面除了isolation(隔离级别)和propagation(传播行为)都很容易理解,这两项也是难理解并且最重要的,后面会单独提到,以上这些属性会被Spring放到事务定义类TransactionDefinition中.
  • 要使用声明式事务,需要在配置文件中开启注解驱动,如下
      Spring框架(编程式事务和声明式事务)_第4张图片

5.2 使用xml进行配置事务管理器

  • xml配置事务管理器的方式很多,但是不常用.下面给出一个例子,它在一定程度上揭露了事务管理器的内部实现,它需要一个事务拦截器–TransactionInterceptor,可以把拦截器想象成AOP编程.
  • 我这里将之前创建的配置文件拷贝一份,单独写xml的配置
  • 配置TransactionInterceptor拦截器
      Spring框架(编程式事务和声明式事务)_第5张图片
  • 重点在于transactionAttributes的内容,SpringIOC启动时会解析这些内容,放到事务定义类TransactionDefinition中,再运行时会根据正则表达式的匹配度决定方法采取哪种策略,这使用了拦截器和SpringAOP的技术,也侧面解释了声明式事务的底层原理---SpringAOP技术.如果你对AOP了解比较少,上面目录12,13,14篇会帮你深入掌握.
  • 上面只是说明了Spring对于方法采用的事务策略,并没有Spring要拦截那些类,需要配置一个类BeanNameAutoProxyCreator
      Spring框架(编程式事务和声明式事务)_第6张图片
  • BeanName属性告诉Spring如何拦截类,由于声明为*ServiceImpl,所有关于Service的实现类都会被拦截,然后Interceptor则是定义事务拦截器,这样对应的类和方法就会被事务管理器所拦截了.

5.3 事务定义器—TransactionDefinition

  • xml和@Transactional中都与事务定义器有密切的联系,下面看一下TransactionDefinition的源码
      Spring框架(编程式事务和声明式事务)_第7张图片
  • 上面就是事务定义器的内容,除了异常的定义,其他关于事务的定义都可以在这里完成,对于事务的回滚,以RollbackRuleAttribute和NoRollbackRuleAttribute两个类进行保存.

5.4 声明式事务@Transactional的流程

  • 为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
  • 可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的.
  • 在 Bean 配置文件中只需要启用 tx:annotation-driven 元素, 并为之指定事务管理器就可以了.
  • 如果事务处理器的id名称是 transactionManager, 就可以在tx:annotation-driven 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
  • 下面写一个使用@Transactional声明事务的例子–买书,买书之后余额减少,书的库存也减少
      注意这里使用JdbcTemplate连接了数据库,需要使用连接数据库的java包,还有就是创建了几个表,具体资料:链接:链接:https://pan.baidu.com/s/14IFSJRzCHwWWdF53C0Y-gw 提取码:9r35
  • 创建SpringIOC的配置文件
<?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:jdbc="http://www.springframework.org/schema/jdbc"
       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.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd">
    <!--设置组件扫描-->
    <context:component-scan base-package="com.tx.test"/>

    <!--1. 配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="10"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="10000"/>
    </bean>
    <!--2. 配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>
  • 创建BookShopDao接口及实现类,定义操作数据库的方法
      Spring框架(编程式事务和声明式事务)_第8张图片
      Spring框架(编程式事务和声明式事务)_第9张图片
  • 创建业务层Service及其实现类包含了一个购买方法
      Spring框架(编程式事务和声明式事务)_第10张图片
      Spring框架(编程式事务和声明式事务)_第11张图片
  • 创建测试类
      Spring框架(编程式事务和声明式事务)_第12张图片
  • 最后,在看一下此时数据库中的数据,发现用户当前金额是不足以支付该图书价格的
      Spring框架(编程式事务和声明式事务)_第13张图片
  • 首先讲明一下代码的运行逻辑,在Dao层使用JdbcTemplate与数据库交互,分别由三个方法,查询书的单价,购买成功后将库存的书减少1,同时将用户的余额扣除掉书的单价,在Service层定义了一个购买方法,需要传递用户名和图书号,这样在执行购买操作时,需要先根据书号查询到书的单价,然后图书数量减少1,并且让用户余额减少对应金额,我们先运行以上代码看看会发生什么
      Spring框架(编程式事务和声明式事务)_第14张图片
  • 执行代码后发现抛出余额不足的异常,这与我们的判断是一致的,那么正常情况下出现这种情况应该怎么做呢?当前是图书库存不减少,同时用户余额也不减少,使购买操作失败,现在重新看一下数据库数据
      Spring框架(编程式事务和声明式事务)_第15张图片
  • 可以看到更新用户余额的方法由于抛出异常而没有正常执行,所有用户余额没有变化,但是图书的库存却减少了,这是为啥呢?
      Spring框架(编程式事务和声明式事务)_第16张图片
  • 这是因为执行更新图书数量的代码中并没有抛出异常,仅仅是检查一下数据库中数量是否为0,不为0则减少,否则抛出异常.以上结果显然不是我们想要结果,我们这时候就应该通过事务将减少余额和减少库存两个操作绑定在一起,任何一个操作出现异常都应该发生回滚,否则提交.
  • 现在我们使用@Transactional注解进行修改
  • 修改配置文件,设置事务管理器,将数据源作为参数传递给事务管理器,并启用事务注解
      Spring框架(编程式事务和声明式事务)_第17张图片
  • 使用@Transactional注解,在BookShopServiceImpl的purchase方法上
      Spring框架(编程式事务和声明式事务)_第18张图片
  • 再次执行测试程序,发现依旧显示余额不足,这在我们的意料之中,再次查看数据库结果,发现余额没有变化,库存保持在上一次的数量8,测试成功
  • 下面我们对示例中的@Transactional注解的使用进行解释(传播行为和隔离级别后面单独详谈)
      在这里同时看不到数据库资源打开和释放的代码,也看不到数据库提交的代码,只看到了注解@Transactional.它配置了Propagation.REQUIRED的传播行为,意味着当别的方法调度时,如果存在事务就沿用下来,如果不存在事务就开启新的事务,而隔离级别采用默认的隔离级别,并设置超时时间为3秒.我们只需要知道当purchase方法发生异常时,Spring就会回滚该方法内执行的所有关于数据库的操作,如果成功则操作一起提交.这样,我们的经历就可以放在业务的开发上,而不是控制数据库的资源和事务上.但是我们必须清楚的是.其底层原理是SpringAOP技术,实现机制是动态代理.
  • 最后贴出一张声明式事务执行的基本流程
      Spring框架(编程式事务和声明式事务)_第19张图片
  • 文字说明流程图:
      1) 首先Spring通过事务管理器(PlatformTransactionManager的子类)创建事务,然后在SpringIOC容器初始化的时候,Spring会读取@Transactional注解或者xml配置的事务信息,将这些隔离级别,超时时间等属性根据配置内容,保存到事务定义器里面(TransactionDefinition接口的子类),然后在事务上配置.根据传播行为采取一定的策略,这些是Spring根据配置完成的内容,我们只需配置好即可
      2) 其次,运行业务代码,Spring会通过反射的方式调用业务代码,默认情况下,如果发生异常且符合回滚条件的便回滚,否则提交事务,这也是Spring自己完成的.
      3) 所以说,整个开发过程中,只需要编写业务代码和对事务属性进行配置即可,工作量很少,代码逻辑也很清晰,有利于维护

5.5 设置回滚事务属性

  • 默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚. 而受检查异常不会.
  • 事务的回滚规则可以通过 @Transactional 注解的rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类.
      rollbackFor: 遇到时必须进行回滚
      noRollbackFor: 一组异常类,遇到时必须不回滚
  • Spring框架(编程式事务和声明式事务)_第20张图片

5.6 超时和只读属性

  • 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.
  • 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
  • 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
  • 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
  • 超时和只读属性可以在 @Transactional 注解中定义.超时属性以秒为单位来计算.
      Spring框架(编程式事务和声明式事务)_第21张图片

你可能感兴趣的