暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring事务控制的深度介绍,你的事务真的生效了吗?

金融科技小站 2017-02-11
480

本文主要针对Spring事务使用过程中的易忽略或不经常被了解的内容做介绍,可以看作是较高级的说明,普通的事务基本概念在这里就不做说明了,下面就开门见山、直入主题。


Spring事务的核心关键类



事务传播行为


PROPAGATION_REQUIRED

该行为是默认的,表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行,否则会启动一个新的事务

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起

PROPAGATION_NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制


事务管理器

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现,其对应的接口是PlatformTransactionManager。下面列举一些常用的事务管理器:


DataSourceTransactionManager

适用于通过JDBC、iBatis、MyBatis中进行数据久化操作的情形。对应需要注入DataSource属性

HibernateTransactionManager

适用于通过Hibernate中进行数据持久化操作的情形。对应需要注入SessionFactory属性

JpaTransactionManager

适用于行使JPA中止数据经久化操作的情形。对应需要注入EntityManagerFactory属性


此外还有JtaTransactionManager 、JdoTransactionManager、JmsTransactionManager等等,但使用的场景较少。


编程事务

Spring中的事务分为物理事务和逻辑事务,物理事务就是指底层数据库提供的事务支持,如JDBC或JTA提供的事务;逻辑事务是Spring管理的事务,不同于物理事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务。逻辑事务既支持非常低级别的控制,也支持高级别解决方案。


低级别解决方案

这里需要使用工具类获取连接和释放连接,如使用org.springframework.jdbc.datasource包中的 DataSourceUtils 类来获取和释放具有逻辑事务功能的连接。当然对集成第三方ORM框架也提供了类似的工具类,如对Hibernate提供了SessionFactoryUtils工具类,JPA的EntityManagerFactoryUtils等,其他工具类都是使用类似***Utils命名。

TransactionAwareDataSourceProxy:使用该数据源代理类包装需要Spring事务管理支持的数据源,该包装类必须位于最外层,主要用于遗留项目中可能直接使用数据源获取连接和释放连接支持或希望在Spring中进行混合使用各种持久化框架时使用,其内部实际使用 DataSourceUtils 工具类获取和释放真正连接。

通过如上方式包装数据源后,可以在项目中使用物理事务编码的方式来获得逻辑事务的支持,即支持直接从DataSource获取连接和释放连接,且这些连接自动支持Spring逻辑事务。


高级别解决方案

使用Spring提供的模板类,如JdbcTemplate、HibernateTemplate和JpaTemplate模板类等,而这些模板类内部其实是使用了低级别解决方案中的工具类来管理连接或会话。Spring提供两种编程式事务支持:直接使用PlatformTransactionManager实现和使用TransactionTemplate模板类,用于支持逻辑事务管理,如果采用编程式事务推荐使用TransactionTemplate模板类和高级别解决方案。


下面是我在一个项目中使用的编程事务控制,其中抽象了一个事务方法调用的工具类,用于处理异常时事务回滚。首先定义事务管理器及事务模板:

定义工具类回调方法:

定义静态工具类,通过spring工具类获取bean,同时执行异常时事务回滚:


声明事务(基于配置文件)

基于配置文件主要基于AOP切面方式进行方法拦截,配置如下:

上面我们看到了,简单的配置了事务,其中tx:attributes中设置了事务的传播性,隔离级别以及那种问题能进行回滚超时等这些问题,也就是你自己按照业务需求定制一个事务来满足你的业务需求(注意: 在tx:method中配置了rollback_for 中配置的Exception 这个是运行时的异常才会回滚不然其他异常是不会回滚的!)。


声明事务(基于注解)

首先需要配置事务管理器并启用事务,对于MyBatis方式的配置如下:

对于Hibernate方式的配置为:

之后便可以在相应的类或方法中基于@Transaction添加事务了(记住需要在配置文件中使用<context:component-scan/>标签添加注解扫描)。


引申:深度注解

对于配置信息也可以通过注解实现,例如在XML中添加<context:spring-configured/>启用注解配置,然后可以在类上添加如下配置:


事务代理机制

Spring的事务管理机制实现的原理,是通过一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。


如下图(网上截取)是通过AOP代理后的代码调用流,调用者首先调用的是AOP代理对象而不是目标对象,首先执行事务切面,事务切面内部通过TransactionInterceptor环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务。

上图可以看到只有被外部调用的被代理方法才能应用事务。对于内部方法调用,由于无法进行方法拦截所以默认事务是无法应用的(可以通过exposeProxy属性的配置及代码的调整实现,但比较复杂不建议使用)。


在实现动态代理方式上主要分为JDK动态代理和CGLIB动态代理,对于基于aspectj的方式暂不介绍。


基于JDK动态代理配置如下,这也是默认的实现。此种方式必须要实现接口的方法,具体配置时,例如@Transaction可以设置到接口或类上。

基于CGLIB动态代理的配置如下,使用时需要引用cglib相关jar包。此种方式不要求必须实现接口,即添加事务的方法可以直接是类、抽象类的方法(但注意方法也需要是public的,而且不是内部调用)。


事务回滚rollbackFor

当方法上配置了事务后,并不是抛出异常就会回滚,默认Spring只会针对Unchecked Exceptions抛出异常。

任何的异常都是Throwable类,并且在它之下包含两个字类Error和Exception。

Unchecked Exceptions指包括 Error与RuntimeException,以及其子类,这些异常抛出时不需要声明或捕获。

Checked Exception指除了Error与RuntimeException,其他剩下的异常都是编译时在语法上必须处理的异常,因此必须在语法上以try..catch或声明throws加以处理。


因此如下要配置的事务在抛出异常时能正确回滚,必须要配置rollbackFor属性,例如可以使用如下三种方式配置:

  • <prop key="update*">PROPAGATION_REQUIRED,-Exception</prop>

  • <tx:method name="insert*" rollback-for="Exception" />

  • @Transactional(rollbackFor=Exception.class)


事务未生效分析

最后总结一下几种事务不生效的原因:

  • 没有配置rollbackFor,在抛出异常时事务没有回滚;

  • @Transaction配置在类或抽象类上,类或抽象类没有实现接口,Spring默认通过AOP(aspectj)的代理进行方法拦截,需要添加proxy-target-class属性并设置为true;

  • 方法是非public方法,非public是不支持声明式事务的;

  • 方法是在内部调用,即A类的X方法调用了Y方法,在Y方法中配置了事务,是不会生效的





文章转载自金融科技小站,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论