Spring事务管理器详解

开发 前端
你还可以通过编程方式指示所需的回滚。尽管这个过程很简单,但它具有很强的侵入性,并将您的代码紧密地耦合到Spring Framework的事务基础设施。

理解Spring事务抽象

Spring事务抽象的关键是事务策略的概念。事务策略是由TransactionManager定义的,特别是用于强制事务管理的
org.springframework.transaction.PlatformTransactionManager接口和用于响应式事务管理的
org.springframework.transaction.ReactiveTransactionManager接口。下面的清单显示了
PlatformTransactionManager API的定义:​

public interface PlatformTransactionManager extends TransactionManager {


TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;


void commit(TransactionStatus status) throws TransactionException;


void rollback(TransactionStatus status) throws TransactionException;
}

这主要是一个服务提供者接口(SPI),尽管你可以从应用程序代码中以编程方式使用它。因为
PlatformTransactionManager是一个接口,所以可以根据需要轻松模拟或存根。它没有绑定到查找策略,比如JNDI。PlatformTransactionManager实现的定义类似于Spring Framework IoC容器中的任何其他对象(或bean)。

同样,为了与Spring的理念保持一致,可以由PlatformTransactionManager接口的任何方法抛出的TransactionException是未检查的(也就是说,它扩展了java.lang.
exception.RuntimeException类)。事务基础结构失败几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获和处理TransactionException。不过对于开发人员并没有强制这样做。

getTransaction(TransactionDefinition)方法返回一个TransactionStatus对象,这取决于TransactionDefinition参数。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可以表示一个新的事务,也可以表示一个现有的事务。后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与执行线程相关联。

从Spring Framework 5.2开始,Spring还为使用响应式类型或Kotlin协程的响应式应用程序提供了事务管理抽象。下面的清单显示了
org.springframework.transaction.ReactiveTransactionManager定义的事务策略:​

public interface ReactiveTransactionManager extends TransactionManager {


Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;


Mono<Void> commit(ReactiveTransaction status) throws TransactionException;


Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

TransactionDefinition接口具体说明:

Propagation(传播特性):通常,事务范围内的所有代码都在该事务中运行。但是,如果在事务上下文已经存在时运行事务方法,则可以指定该行为。例如,代码可以继续在现有事务中运行(常见情况),也可以暂停现有事务并创建新事务。

Isolation(隔离性):该事务与其他事务的工作隔离的程度。例如,这个事务能看到其他事务未提交的写吗?

Timeout(超时时间):该事务在超时和被底层事务基础结构自动回滚之前运行的时间。

Read-Only(只读状态):当代码读取但不修改数据时,可以使用只读事务。在某些情况下,例如使用Hibernate时,只读事务可能是一种有用的优化。

这些设置反映了标准的事务概念。如有必要,请参考讨论事务隔离级别和其他核心事务概念的资源。理解这些概念对于使用Spring框架或任何事务管理解决方案都是至关重要的。

TransactionStatus接口为事务代码提供了一种控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对所有事务api都很常见。下面的清单显示了TransactionStatus接口:​

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {


@Override
boolean isNewTransaction();


boolean hasSavepoint();


@Override
void setRollbackOnly();


@Override
boolean isRollbackOnly();


void flush();


@Override
boolean isCompleted();
}

无论在Spring中选择声明式事务管理还是编程式事务管理,定义正确的TransactionManager实现都是绝对必要的。通常通过依赖注入来定义此实现。

TransactionManager实现通常需要了解它们工作的环境:JDBC、JTA、Hibernate等等。下面的示例展示了如何定义本地
PlatformTransactionManager实现(在本例中,使用普通JDBC)。

首先,你的先定义一个数据源:

<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

Java Config:​

@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource() ;
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true") ;
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver") ;
dataSource.setUsername("root") ;
dataSource.setPassword("root") ;
return dataSource ;
}

相关的
PlatformTransactionManager bean定义随后具有对DataSource定义的引用。它应该类似于下面的例子:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

Java Config:​

@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);
return txManager;
}

Hibernate事务配置

还可以轻松地使用Hibernate本地事务,如以下示例所示。在这种情况下,需要定义一个Hibernate LocalSessionFactoryBean,应用程序代码可以使用它来获取Hibernate会话实例。

DataSource bean定义类似于前面显示的本地JDBC示例

本例中的txManager bean属于HibernateTransactionManager类型。就像DataSourceTransactionManager需要对DataSource的引用一样,HibernateTransactionManager也需要对SessionFactory的引用。下面的例子声明了sessionFactory和txManager bean:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>


<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>

LocalSessionFactoryBean是个FactoryBean,同时实现了InitializingBean接口,所以在当前类初始化的时候,会调用afterPropertiesSet方法,该方法中会初始化SessionFactory对象。

将资源与事务同步

现在应该清楚了如何创建不同的事务管理器,以及如何将它们链接到需要同步到事务的相关资源(例如,DataSourceTransactionManager到JDBC DataSource,Hibernate TransactionManager到Hibernate SessionFactory,等等)。本节描述了应用程序代码(直接或间接地,通过使用JDBC、Hibernate或JPA等持久性API)如何确保正确创建、重用和清理这些资源。本节还讨论了如何(可选地)通过相关的TransactionManager触发事务同步。

  • 高级同步方法

首选方法是使用Spring的最高级别基于模板的持久性集成API,或者使用具有事务感知工厂bean或代理的本地ORM API来管理本地资源工厂。这些事务感知解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步以及异常映射。因此,用户数据访问代码不必处理这些任务。通常,使用本机ORM API,或者通过使用JdbcTemplate采用模板方法进行JDBC访问。

  • 低级同步方法

DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils(适用于JPA)、SessionFactoryUtil(适用于Hibernate)等类存在于较低级别。当您希望应用程序代码直接处理本机持久性API的资源类型时,可以使用这些类来确保获得正确的Spring Framework托管实例,同步事务(可选),并将过程中发生的异常正确映射到一致的API。

例如,在JDBC的情况下,你可以使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,而不是在数据源上直接调用getConnection()方法,如下所示:​

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有事务已经有一个同步(链接)到它的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接(可选地)同步到任何现有事务,并可用于同一事务中的后续重用。如前所述,任何SQLException都被包装在Spring框架
CannotGetJdbcConnectionException中,这是Spring框架中未检查的DataAccessException类型的层次结构之一。这种方法提供了比从SQLException轻松获得的更多信息,并确保了跨数据库、甚至跨不同持久性技术的可移植性。

这种方法在没有Spring事务管理的情况下也可以工作(事务同步是可选的),因此无论是否使用Spring进行事务管理,都可以使用它。

当然,一旦你使用了Spring的JDBC支持、JPA支持或Hibernate支持,你通常不喜欢使用DataSourceUtils或其他辅助类,因为你更喜欢通过Spring抽象工作而不是直接使用相关的api。例如,如果使用Spring JdbcTemplate或jdbc。对象包来简化你的JDBC使用,正确的连接检索发生在幕后,你不需要编写任何特殊的代码。

  • TransactionAwareDataSourceProxy

在最底层存在
TransactionAwareDataSourceProxy类。这是目标DataSource的代理,它封装目标DataSource以添加对Spring托管事务的感知。

除非必须调用现有代码并传递标准JDBC DataSource接口实现,否则几乎你不需要或不想使用这个类。在这种情况下,这段代码可能是可用的,但参与了spring管理的事务。你可以使用前面提到的高级抽象来编写新代码。

声明式事务管理

Spring Framework的声明式事务管理是通过Spring面向方面编程(AOP)实现的。然而,由于事务方面的代码是随Spring Framework一起提供的,并且可以以样板的方式使用,因此通常不需要理解AOP概念就可以有效地使用这些代码。

仅仅告诉你使用@Transactional注解注释的类,将@
EnableTransactionManagement添加到你的配置中,并期望你理解它是如何工作的,这是不够的。为了加深理解,本节将在与事务相关的问题上下文中解释Spring框架的声明式事务基础结构的内部工作原理。

关于Spring Framework的声明性事务支持,需要掌握的最重要的概念是,这种支持是通过AOP代理启用的,并且事务Advice是由元数据驱动的(目前是基于XML或注释的)。AOP与事务元数据的组合产生了一个AOP代理,该代理使用TransactionInterceptor和适当的TransactionManager实现来围绕方法调用驱动事务。

Spring Framework的TransactionInterceptor为命令式和反应式编程模型提供事务管理。拦截器通过检查方法返回类型来检测所需的事务管理风格。返回响应式类型(如Publisher或Kotlin Flow(或其子类型))的方法符合响应式事务管理的条件。包括void在内的所有其他返回类型都使用代码路径进行强制事务管理。

事务管理会影响所需的事务管理器。强制事务需要PlatformTransactionManager,而响应事务使用ReactiveTransactionManager实现。

@Transactional通常使用
PlatformTransactionManager管理的线程绑定事务,将事务暴露给当前执行线程中的所有数据访问操作。注意:这不会传播到方法中新启动的线程。


ReactiveTransactionManager管理的反应事务使用Reactor上下文而不是线程本地属性。因此,所有参与的数据访问操作都需要在同一反应管道中的同一Reactor上下文中执行。

下图显示了在事务代理上调用方法的概念视图:

图片


  • 声明性事务实现示例

考虑以下接口及其附属实现。本例使用Foo和Bar类作为占位符,这样你就可以专注于事务的使用,而不必关注特定的域模型。就本例而言,DefaultFooService类在每个实现方法的主体中抛出
UnsupportedOperationException实例是好的。该行为允许你查看正在创建的事务,然后回滚以响应UnsupportedOperationException实例。FooService接口如下所示:​

package x.y.service;


public interface FooService {


Foo getFoo(String fooName);


Foo getFoo(String fooName, String barName);


void insertFoo(Foo foo);


void updateFoo(Foo foo);


}

接口实现:​

package x.y.service;


public class DefaultFooService implements FooService {


@Override
public Foo getFoo(String fooName) {
// ...
}


@Override
public Foo getFoo(String fooName, String barName) {
// ...
}


@Override
public void insertFoo(Foo foo) {
// ...
}


@Override
public void updateFoo(Foo foo) {
// ...
}
}

以FooService接口的前两个方法getFoo(String)和getFoo为例配置事务拦截。​

<?xml versinotallow="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"
xsi:schemaLocatinotallow="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="fooService" class="x.y.service.DefaultFooService"/>


<!-- 事务通知配置->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 事务语义 -->
<tx:attributes>
<!-- 所有以get开头的方法的事务都是只读的-->
<tx:method name="get*" read-notallow="true"/>
<!-- 其它事务都使用事务的默认行为 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="fooServiceOperation" expressinotallow="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

<aop:config/>定义确保txAdvice bean定义的事务建议在程序中的适当点运行。首先,定义一个切入点,该切入点与FooService接口(fooServiceOperation)中定义的任何操作的执行相匹配。然后使用advisor将切入点与txAdvice关联起来。结果表明,在执行fooServiceOperation时,运行由txAdvice定义的通知。

一个常见的要求是使整个服务层具有事务性。做到这一点的最佳方法是更改切入点表达式以匹配服务层中的任何操作。以下示例显示了如何执行此操作:​

<aop:config>
<aop:pointcut id="fooServiceMethods" expressinotallow="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
  • 回滚声明性事务

上面介绍了如何在应用程序中以声明方式为类(通常是服务层类)指定事务设置的基础知识。本节描述如何在XML配置中以简单的声明式方式控制事务的回滚。

向Spring框架的事务基础设施指示事务的工作要回滚的推荐方法是从当前在事务上下文中执行的代码抛出异常。Spring框架的事务基础结构代码在调用堆栈中弹出气泡时捕获任何未处理的异常,并确定是否将事务标记为回滚。

在默认配置中,Spring框架的事务基础结构代码仅在运行时未检查异常的情况下将事务标记为回滚。也就是说,当抛出的异常是RuntimeException的实例或子类时。(默认情况下,错误实例也会导致回滚)。从事务方法抛出的已检查异常不会导致默认配置中的回滚。

你可以通过指定回滚规则,准确地配置哪些Exception类型标记要回滚的事务,包括已检查的异常。

回滚规则

回滚规则确定在抛出给定异常时是否应该回滚事务,这些规则基于模式。模式可以是完全限定类名,也可以是异常类型(必须是Throwable的子类)的完全限定类名的子字符串,目前不支持通配符。例如,"
javax.servlet.ServletException"或"ServletException"将匹配javax.servlet.ServletException及其子类。

可以通过Rollback-for和no-rollback-for属性在XML中配置回滚规则,这些属性允许将模式指定为字符串。当使用@Transactional时,可以通过rollbackFor/noRollbackFor和
rollbackForClassName/noRollbackForClassName属性来配置回滚规则,这些属性允许分别将模式指定为类引用或字符串。当异常类型被指定为类引用时,其全限定名将用作模式。因此,@Transactional(rollbackFor = example.CustomException.class)等价于@Transactional(rollbackForClassName = "example.CustomException")。

以下XML片段演示了如何通过回滚for属性提供异常模式,为已检查的、特定于应用程序的异常类型配置回滚:​

<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-notallow="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

如果不希望在引发异常时回滚事务,也可以指定“无回滚”规则。下面的示例告诉Spring Framework的事务基础结构即使面对未处理的
InstrumentNotFoundException也要提交附带事务:​

<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

当Spring Framework的事务基础设施捕捉到异常并参考配置的回滚规则以确定是否将事务标记为回滚时,最强的匹配规则获胜。因此,在以下配置的情况下,
InstrumentNotFoundException以外的任何异常都会导致伴随事务的回滚:​

<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>

你还可以通过编程方式指示所需的回滚。尽管这个过程很简单,但它具有很强的侵入性,并将您的代码紧密地耦合到Spring Framework的事务基础设施。以下示例显示了如何以编程方式指示所需的回滚:​

public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// 调用setRollbackOnly进行回滚设置
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}


责任编辑:武晓燕 来源: 实战案例锦集
相关推荐

2009-09-29 09:44:52

Hibernate事务

2009-06-08 17:56:00

SpringJDBC事务

2009-06-30 16:57:42

Spring事务管理

2009-06-17 14:57:11

Spring事务管理

2023-10-08 08:28:10

Spring事务管理

2009-11-06 11:21:21

WCF事务管理器

2014-08-25 09:12:47

Spring事务管理

2010-03-29 13:34:15

ibmdwSpring

2010-03-23 08:46:40

Spring

2009-09-25 12:59:53

Hibernate事务

2009-02-11 11:14:31

事务管理事务开始Spring

2009-02-11 13:08:29

事务提交事务管理Spring

2009-06-17 14:43:47

Spring框架Spring事务管理

2023-04-02 13:57:04

Java自定义事务管理器

2009-09-23 17:48:00

Hibernate事务

2009-06-03 10:20:11

Hibernate事务管理配置

2023-05-06 07:29:49

Spring事务传播

2009-07-17 14:03:34

ibatis DAO事务管理

2009-12-24 16:01:17

redhat Linu

2022-08-04 08:46:16

单体架构微服务事务管理
点赞
收藏

51CTO技术栈公众号