欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring 数据库事务管理

程序员文章站 2022-03-30 11:52:26
...

Spring 数据库事务管理器的设计
Spring中数据库事务是通过PlatformTransactionManager进行管理的,像jdbcTemplate它自身是不能支持事务的,而能够支持事务的是 org.springframework.transaction.support.TransactionTemplate模版,它是Spring所提供的事务管理器的模版。

  • 事务的创建、提交和回滚是通过PlatformTransactionManager接口来完成的。
  • 当事务产生异常时会回滚事务,在默认的实现中所有的异常都会回滚。我们可以通过配置去修改在某些异常发生时回滚或者不回滚事务。
  • 当无异常时,会提交事务。

同时事务管理器也有多个,常用的有DataSourceTransactionManager,它继承抽象事务管理器AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager又实现了PlatformTransactionManager。
其中PlatformTransactionManager接口中就三个方法:

  1. 获取事务的状态
    TransactionSattus getTransaction(TransactionDefinition definition) throws TransactionException;
  2. 提交事务
    void commit(TransactionStatus status) throws TransactionException;
  3. 回滚事务
    void rollback(TransactionStatus status) throws TransactionException;

配置事务管理器
MyBatis框架,用的最多的事务管理器是DataSourceTransactionManager
(org.springframework.jdbc.datasource.DataSourceTransactionManager),如果使用的持久框架Hibernate,那么你就要用到spring-orm包org.springframework.orm.hibernate4.HibernateTransactionManager了。
在appliationContext.xml中配置如下:

  • xsi:schemaLocation头部标签里面加上相关的 tx连接
  • dataSource数据库连接池相关配置
  • 配置数据源事务管理器
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    p:dataSource-ref="dataSource"/>

解释:

  • 先引入XML的命名空间,然后定义了数据库连接池,于是使用了DataSourceTransactionManager去定义数据库事务管理器,并且注入了数据库连接池。这样Spring就知道你已经将数据库事务委托给事务管理器 transactionManager管理了。
  • 通过了解jdbcTemplate,数据库资源的产出和释放如果没有委托给数据库管理器,那么就由jdbcTemplate管理,但是委托给事务管理器管理后,jdbcTemplate就不用了。

在Spring中可以使用声明式事务或者编程式事务,编程式事务这里不再描述。
声明式事务又可以分为XML配置和注解事务,但XML方式也已经不常用了,目前主流的是用注解@Transaction。

声明式事务

编程式事务是一种约定型的事务,在大部分情况下,当使用数据库事务时,大部分的场景是在代码中发生了异常时,需要回滚事务,而不发生异常时则是提交事务,从而保证数据库的一致性。声明式事务就是,当你的业务方法不发生异常(或者发生异常,但该异常也被配置信息允许提交事务)时,Spring就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。

Transaction 的配置

配置项 含义 备注
value 定义事务管理器 它是Spring Ioc里的一个Bean id,这个Bean需要实现接口
transactionManager 同上 同上
isolation 隔离级别 这是一个数据库在多个事务同时存在的概念,默认取数据库默认隔离级别
propagation 传播行为 传播行为是方法之间调用的问题。默认值Propagation.REQUIRED
timeout 超时时间 单位为秒,当超时时,会引发异常,默认会导致事务回滚
readOnly 是否开启只读事务 默认值 false
rollbackFor 回滚事务的异常类定义 也就是只有当方法产生所定义异常时,才回滚事务,否则就提交事务
rollbackForClassName 回滚事务的异常类名定义 同rollbackFor,只是使用类名称定义
noRollbackFor 当产生哪些异常不回滚事务 当产生所定义的异常时,Spring将继续提交事务
noRollbackForClassName 同noRollbackFor 同noRollback,只是使用类的名称定义

备注:
这些属性将会被Spring放到事务定义类TransactionFefinition中,事务声明器的配置内容也是以这些为主了。
注意,使用声明式事务需要配置注解驱动,只需要在上述的applicationContext.xml基础上加上:

<!-- 启用对事务注解的支持 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

就可以使用@Transaction配置事务了。

声明式事务的约定流程

@Transaction注解可以使用在方法或者类上面,在Spring Ioc容器初始化时,Spring会读入这个注解或者XML配置的事务信息,并且保存到一个事务定义类里面(TransactionDefinition接口的子类),以备将来使用。当运行时会让Spring拦截注解标注的某一个方法或者类的所有方法。

流程:
1、首先,Spring通过事务管理器(PlatformTransactionManager的子类)创建事务,与此同时会把事务定义中的隔离级别、超时时间等属性
根据配置内容往事务上设置。
2、然后,启动开发者提供的业务代码,我们知道Spring会通过反射的方式调度开发者的业务代码,但是反射的结果可能是正常返回或者产生异常返回,
那么给他的约定是只要发生异常,并且符合事务定义类回滚条件的,Spring就会将数据库事务回滚,否则将数据库事务提交。

选择隔离级别和传播行为

其出发点在于两点:性能和数据一致性

选择隔离级别

  • 在系统应用中,不但要考虑数据库的数据的一致性,而且要考虑系统的性能。
    从脏读到序列化,系统性能直线下降。因此设置搞的级别,比如序列化,会严重压制并发,从而引发大洋的线程挂起,知道获得锁才能进一步操作,而回复时有需要大量的等待时间。在大部分场景下,企业会选择 读/写提交的方式设置事务。这样既有助于提高并发,又压制了脏读,但是对于数据一致性问题并没有很好的解决。
  • 总之,隔离级别需要根据并发的大小和性能来做出决定,对于并发不大又要保证数据安全性的可以使用序列化的隔离界别,这样能够保证数据库在多事务环境中的一致性。
//设置方法为 读/写提交的隔离级别
    @Override
    @Transactional(propagation=Propagation.REQUIRED,
    isolation=Isolation.READ_COMMITTED)
    public void saveAnalyst(Analyst analyst) {

        analystMapper.insertSelective(analyst);
    }
//设置方法为序列化的隔离级别
    @Override
    @Transactional(propagation=Propagation.REQUIRED,
    isolation=Isolation.SERIALIZABLE)
    public void saveAnalyst(Analyst analyst) {

        analystMapper.insertSelective(analyst);
    }

@Transaction注解隔离级别默认为Isolcation.DEFAULT,其含义是默认的,随着数据库的默认值的变化而变化。因为数据库不同,隔离级别的支持也不一样。比如,MySQL可以支持4种隔离级别,而默认的是可重复读的隔离级别。而Oracle只能支持 读/写提交和序列化两种隔离级别,默认为 读/写提交。

传播行为

传播行为是指方法之间的调用事务策略的问题。
一个方法调度另外一个方法时,可以对事务的特性进行传播配置,我们称为传播行为。
在Spring中传播行为的类型是通过一个枚举类型去定义的,这个枚举是org.springframework.transaction.annotation.Propagation

传播行为 含义 备注
REQUIRED 当方法调用时,如果不存在当前事务,那么就创建 事务;如果之前的方法已经存在事务了,那么就沿用之前的事务。 这是Spring默认的传播行为
SUPPROTS 当方法调用时,如果不存在当前事务,那么不启用事务;如果存在当前事务,那么就沿用当前事务。 -
MANDATORY 方法必须要在事务内运行 如果不存在当前事务,那么就抛出异常
REQUIRES_NEW 无论是否存在当前事务,方法都会在新的事务中运行 事务管理器会打开新的事务运行该方法
NOT_SUPPORTED 不支持事务,如果不存在当前事务也不会创建事务;如果存在当前事务,则挂起它,直到该方法结束后才恢复当前事务 适用于那些不需要事务的SQL
NEVER 不支持事务,只有在没有事务的环境中才能运行它 如果方法存在当前事务,则抛出异常
NESTED 嵌套事务,也就是调用方法如果抛出异常只会回滚自己内部执行的SQL,而不回滚主方法的SQL。 它的实现存在两种情况,如果当前数据库支持存点,那么它就会在当前事务上使用保存点技术;如果发生异常则将异常方法内部执行的SQL回滚到保存点上,而不是全部回滚,否则就等同于REQUIRES_NEW创建新的事务运行方法代码

@Transaction自调用失效问题
有时候配置了注解@Transaction,但是它会失效,这里需要注意一些细节问题。
注解@Transaction的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这意味着对于静态(static)方法和非public方法,注解@Transaction是失效的。
自调用失效问题。就是一个类的一个方法调用自身另外一个方法的过程。

典型的事务注解用法错误问题

  • 错误使用Service,在Controller的一个方法中同时调用多次相同方法
    原因:会出现一个业务在两个事务中完成
  • 过长时间占用事务
    把一些跟数据库事务无关的业务拿出来,不放到事务中
  • 错误捕捉异常
    有时候我们会在service业务中加 try…catch…语句,所以Spring在数据库事务所约定的流程中再也得不到任何异常信息了,此时Spring就会提交事务。
    所以我们要自行抛出异常,让Spring事务管理流程获取异常,进行事务管理:
    throw new RuntimeException(ex)