Spring事务

Spring事务

Spring对事务管理的支持

对编程式事务的支持

Spring在高层次建立了统一的事务抽象。不管用户选择Spring JDBCHibernateJPA还是MyBatisSpring都可以使用统一的编程模型进行事务管理。Spring提供了事务模板类TransactionTemplate,通过TransactionTemplate并配合TransactionCallback指定具体的持久化操作,就可以通过编程方式实现事务管理,而无需关心连接获取、释放、复用、事务同步和异常处理等操作。

对声明式事务的支持

Spring允许通过声明方式在IoC配置指定事务边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。

JTA事务的支持

在多数据源情况下,Spring通过引用应用服务器中的JNDI资源完成JTA事务。而在大多数应用中使用的是单数据源,在这种情况下Spring自身接管事务控制。关键在于,不管是单数据源的普通事务还是多数据源情况下的JTA事务,在Spring中可以使用相同的编程模型,这一好处是不可估量的。

事务关键抽象

Spring事务管理SPI(Service Provider Interface)的抽象层主要提供了三个接口:分别是 PlatformTransationManagerTransactionDefinitionTransactionStatus。其关系如下:

transationmanagerapi.png

TransactionDefinition为事务定义,用于描述事务隔离级别、事务传播机制、事务超时等事务属性,这些事务属性可以通过应用上下文扫描注解或读取配置文件获得。PlatformTransactionManager根据TransactionDefinition提供的事务属性创建事务,并用TransactionStatus描述激活事务的状态。

事务定义

属性 说明
propagationBehavior 事务传播属性,默认传播属性为TransactionDefinition.PROPAGATION_REQUIRED
isolationLevel 事务隔离级别,默认隔离级别与数据库相关
timeout 事务超时时间,事务运行时间大于该配置后将被回滚,有些事务管理器不支持事务过期功能,会抛出异常.
readOnly 只读事务不修改任何数据,尝试修改数据将会发生异常

事务运行状态

TransactionStatus代表一个事务的具体运行状态。事务管理器可以通过该接口获取事务运行期的状态,也可以通过该接口间接地回滚事务,它相比于抛出异常时回滚更具可控性。该接口继承于SavepointManager,其基于JDBC3.0保存点的分段事务控制能力提供了嵌套事务控制。SavepointManager接口方法如下:

方法 说明
Object createSavepoint() 创建一个保存点
void releaseSavepoint() 释放一个保存点,事务提交后所有保存点将自动释放
void rollbackToSavepoint(Object savepoint) 将事务回滚到特定的保存点上,被回滚的保存点将自动释放

TransactionStatus扩展了SavepointManager,提供以下方法:

方法 说明
boolean hasSavepoint() 判断当前事务是否在内部创建了保存点,保存点一般是为了支持Spring嵌套事务而创建的
boolean isNewTransaction() 判断当前事务是否是新的事务。如果返回false,说明当前事务是已经存在的事务,或当前操作运行在非事务环境中
boolean isCompleted() 判断当前事务是否已经结束(提交或回滚)。
boolean isRollbackOnly() 判断当前事务是否被标记为rollback-only
void setRollbackOnly() 将当前事务标记为rollback-only,通过该标识通知事务管理器当前事务只能回滚,事务管理器将显式调用回滚操作或抛出异常的方式回滚。

事务管理器

PlatformTransactionManager是事务管理的高层抽象接口,其只定义了三个接口方法:

方法 说明
TransactionStatus getTransaction(TransactionDefinition definition) 从事务环境中获取一个事务或新建一个事务
void commit(TransactionStatus status) 根据事务状态提交事务,如果事务已被标识为rollback-only,则执行回滚操作
void rollback(TransactionStatus status) 回滚事务,如果commit()方法抛出异常,则隐式调用回滚事务。

事务管理器实现类

事务管理器实现 说明
org.springframework.orm.jpa.JpaTransactionManager
org.springframework.orm.jpa.JpaTransactionManager 使用JPA进行持久化时使用该事务管理器
org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate进行持久化时使用该事务管理器
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBCMyBatis
进行持久化时使用该事务管理器
org.springframework.transaction.jta.JtaTransactionManager 具有多个数据源的全局事务使用该事务管理器,不管使用何种持久化技术

事务同步管理器

SpringJDBCConnectionHibernateSession等访问数据库的连接或会话统称为资源。这些资源不是线程安全的,同一时刻不能被多个线程共享。为了让ServiceDao类能做到SingletonSpring事务同步管理器(org.springframework.transaction.support.TransactionSynchronizationManager)使用ThreadLocal为不同事务线程提供不同的资源副本,同时维护事务的配置属性和运行状态。

Spring将持久化技术中影响线程安全的所有非安全因素统一抽取到TransactionSynchronizationManager类中,并用ThreadLocal进行替换,使这些类摘掉了非线程安全的帽子。

public abstract class TransactionSynchronizationManager {
	//用于保存session或connection等资源
   private static final ThreadLocal<Map<Object, Object>> resources =
         new NamedThreadLocal<>("Transactional resources");
	//
   private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
         new NamedThreadLocal<>("Transaction synchronizations");
	//保存事务线程对应的事务名称
   private static final ThreadLocal<String> currentTransactionName =
         new NamedThreadLocal<>("Current transaction name");

   private static final ThreadLocal<Boolean> currentTransactionReadOnly =
         new NamedThreadLocal<>("Current transaction read-only status");

   private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
         new NamedThreadLocal<>("Current transaction isolation level");

   private static final ThreadLocal<Boolean> actualTransactionActive =
         new NamedThreadLocal<>("Actual transaction active");
}

为进一步方便用户使用,Spring为不同的持久化技术提供了一套以线程安全方式从同步事务管理器TransactionSynchronizationManager获取资源的工具类,如下:

| ORM

持久化技术 工具类
Hibernate org.springframework.orm.hibernate5.SessionFactoryUtils
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils
JDBC
MyBatis org.springframework.jdbc.datasource.DataSourceUtils

事务传播行为

当我们调用基于SpringService接口方法时,该Service接口内部可能调用其他的Service接口方法,就可能产生服务接口方法嵌套调用的情况。Spring通过事务传播行为控制当前接口的事务如何传播到被调用的接口方法上。SpringTransactionDefinition中定义了7种事务传播行为:

事务传播行为 说明
PROPAGATION_REQUIRED 默认事务传播属性。如果存在当前事务,加入到当前事务中,如果没有则新建一个事务
PROPAGATION_SUPPORTS 支持事务。如果存在当前事务,加入到当前事务中;如果没有则以非事务方式运行
PROPAGATION_MANDATORY 使用当前事务。如果存在当前事务,加入到当前事务中;如果没有则抛出异常
PROPAGATION_REQUIRES_NEW 新建事务。如果当前存在事务,则将当前事务挂起,然后新建事务运行;如果没有则新建事务运行。
PROPAGATION_NOT_SUPPORTED 不支持事务。如果存在当前事务,则将当前事务挂起,并以非事务方式运行;
PROPAGATION_NEVER 不支持事务。如果存在当前事务,则抛出异常
PROPAGATION_NESTED 嵌套事务。如果存在当前事务,则在嵌套事务内执行,如果没有,则新建一个事务。与PROPAGATION_REQUIRED
的区别是,嵌套事务支持基于保存点的分段回滚或提交

编程式事务

Spring为编程式事务提供了模板类org.springframework.transaction.support.TransactionTemplate来方便用户使用。该模板类与其他Dao模板类 一样是线程安全的,因此可以在多个业务类中共享TransactionTemplate实例进行事务管理。TransactionTemplate继承了TransactionDefinition,用户可以直接使用其设置相关的事务属性,同时其提供了两个重要的方法:

方法 说明
void setTransactionManager(PlatformTransactionManager transactionManager) 设置事务管理器
Object execute(TransactionCallback action) TransactionCallback中定义需要以事务方式执行的数据访问逻辑

TransactionCallback 接口中只有一个方法:Object doInTransaction(TransactionStatus status),在该方法中编写具体的持久化事务逻辑。Demo如下:

@Service
public class ForumService {
   @Autowired
   private ForumDao forumDao;
   @Autowired
   private TransactionTemplate template;
    
   public void addForum(final Forum forum) {
      template.execute(new TransactionCallbackWithoutResult() {
         @Override
         protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
            forumDao.addForum(forum);
         }
      });
   }

}

由于Spring事务管理是基于TransactionSynchronizationManager进行工作的,所以如果在TransactionCallbackdoInTransaction(TransactionStatus status)方法中显示访问事务资源(如ConnectionSession等),则必须通过相应的工具类进行获取,否则将会造成不可预料的错误。这是Spring事务管理的底层规定,不容违反。在上面的代码示例中,如果ForumDao中想要获取ConnectionSession等相关事务资源,即必须通过工具类(DataSourceUtilsSessionFactoryUtils等)或者数据访问模板类(JdbcTemplateHibernateTemplate等)获取。

声明式事务

通过TransactionProxyFactoryBean配置声明式事务

一个典型的配置如下:

 <!--需要事务增强的业务类-->
    <bean id="forumServiceTarget" class="com.zzvcom.aspectj.expression.bean.ForumService"></bean>

    <!--事务管理器-->
    <bean  id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--使用事务代理工厂类为目标类提供事务增强-->
    <bean class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!--指定事务管理器-->
        <property name="transactionManager" ref="txManager"></property>
        <!--指定增强的业务不妙类-->
        <property name="target" ref="forumServiceTarget"></property>
        <!--配置事务属性-->
        <property name="transactionAttributes">
            <props>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

使用TransactionProxyFactoryBean配置业务增强bean必须为其配置增强的目标bean、事务管理器和事务属性等信息。Spring允许通过键值配置业务方法的事务属性,键可以使用通配符。如 get*匹配ForumService中的所有以get开头的方法,*匹配所有方法内的值为事务属性,其配置格式如下:

transactionproperties.png

传播行为是唯一必须的配置项。当的值为空时,也不会发生异常,但是此时匹配的方法不会应用事务。隔离级别是可选的,默认为ISOLATION_DEFAULT,表示使用数据库默认的隔离级别。如果希望将匹配的方法的事务属性设置为只读,则添加readOnly配置项即可。

事务的异常处理

当事务在运行过程中发生异常时,事务可以根据其声明进行回滚或提交。在默认情况下,当发生运行时异常(非受检异常)时,事务将被自动回滚,当发生检查型异常时,既不会自动回滚也不会自动提交,控制权将交由外层调用者。这种默认规则在大多数情况下是适用的,不过用户也可以显式指定回滚规则。

在配置文件配置的声明式事务中,可以指定带正号(+)或负号(-)的异常类名进行指定。如下:

<prop key="get*">PROPAGATION_REQUIRED,readOnly,+ConcurrentFailureException,-CleanupFailureException</prop>

当抛出负号型异常时,事务将被回滚,当抛出正号型异常时,事务将被提交。不过需要注意的时,当抛出正号型异常时,事务虽然被提交,但是抛出异常后的代码将不会被执行,也就是说被提交的事务可能是整个完整事务的一部分。

public void addTopic(Topic topic,Forum forum) {
   topicDao.addTopic(topic);
   throw new ConcurrentModificationException();
    //异常后续的代码将得不到执行,但事务依然被提交
   forumDao.updateForum(forum)
}

使用TransactionProxyFactoryBean配置声明式事务需要为每一个业务类配置一次该FactoryBean,而且在指定事务方法时,只能通过方法名指定(PROPAGATION_REQUIRED)。该方式已被抛弃。

使用txaop命名空间配置声明式事务

<!--事务管理器-->
<bean  id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务增强-->
<tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" <!--指定事务属性应用到哪些方法上-->
                     isolation="REPEATABLE_READ" <!--隔离性-->
                     propagation="NESTED"  <!--传播属性-->
                     read-only="true" <!--只读事务-->
                     timeout="300"  <!--事务超时时间 以秒为单位-->
                     no-rollback-for="ConcurrentFailureException" <!--不触发回滚的异常-->
                     rollback-for="CleanupFailureException"/><!--触发回滚的异常-->
        </tx:attributes>
</tx:advice>
<!--使用aop定义目标方法-->
<aop:config>
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.zzvcom..*.*(..))"/>
</aop:config>

使用@Transactional注解配置声明式事务

<!--事务管理器-->
<bean  id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--对标注Transactional注解的bean进行加工处理,织入事务切面-->
<tx:annotation-driven
        transaction-manager="txManager" <!--指定事务管理器-->
        mode="aspectj"
        order="1"
        proxy-target-class="true"/>

Annotation driven transaction settings_如下:

XML Attribute Default Description
transaction-manager transactionManager 指定事务管理器Bean
mode proxy proxy模式使用Spring AOP框架进行代理(方法只有在外部调用时才会被代理,内部调用无法代理)。aspectj模式使用 Spring’s AspectJ进行代理,该模式通过修改目标类字节码产生代理,因此可以代理任何形式的方法调用。AspectJ 织入需要 spring-aspects.jar 并且开启load-time weavingcompile-time weaving
proxy-target-class false 只有在proxy模式下才会起作用。如果为true,Spring将通过创建目标类的子类的方式产生代理bean,如果为false,Spring将使用标准的JDK动态代理(创建基于接口的代理)。
order Ordered.LOWEST_PRECEDENCE 指定代理类的循序

Spring建议在具体的业务实现类上使用@Transactional注解,而不是在接口上。因为接口上的注解不能被实现类继承,因而会导致部分事务代理失效,导致不必要的麻烦。

posted @ 2023-01-31 14:53  小张同学哈  阅读(16)  评论(0)    收藏  举报