MENU

谈谈 Spring 中的事务

June 7, 2018 • Code

前言: 了解事务的基本概念,同时理解 Spring 对事务的管理。

事务

来自维基:

数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

概括来说:事务是一系列对数据库操作原子序列。

事务的 ACID

通过上面的阐述可知,事务的本质是操作序列,但并不是所有的操作序列都是事务。事务需要有四个基本属性(即 ACID):

  • Atomicity:原子性。事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

  • Consistency:一致性。事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足 完整性约束

  • Isolation:独立性。多个事务并发执行时,一个事务的执行不应影响其他事务的执行

  • Durability:持久性。已被提交的事务对数据库的修改应被永久保存在数据库中。

并发问题

指事务在并发的情况下会出现的问题,主要有三种:

  1. 脏读:一个事务还未提交,另外一个事务访问此事务修改的数据,并使用,读取了事务中间状态数据。

  2. 不可重复读:一个事务读取同一条记录 2 次,得到的结果不一致,由于在 2 次读取之间另外一个事务对此行数据进行了修改。

  3. 幻读:一个事务读取 2 次,得到的记录条数不一致,由于 2 次读取之间另外一个事务对数据进行了增删。

事务的隔离级别

来自百科:

隔离级别:一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读或虚拟读取)的角度进行描述。

即隔离级别即指采用何种策略来控制锁的程度,从而解决上述事务并发问题。

事务隔离级别 存在的问题
Read Uncommitted 脏读、不可重复读、幻读
Read Committed 脏读、不可重复读、幻读
Repeatable Read 脏读、不可重复读、幻读
Serializable 脏读、不可重复读、幻读

Spring 事务管理

Spring 中的事务管理一般可分三层:DataSource,事务管理器及代理层,下述不同形式的事务管理配置也是在这一层有所不同。

Spring 事务的传播行为

事务传播行为描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

举个栗子:

public void A () {
    //开启事务 Ta
    open();
    
    // 调用 B
    B();
    
    // 关闭事务 Ta
    close();
}

public void B () {
    //开启事务 Tb
    open();
    
    doSomething();
    
    // 关闭事务 Tb
    close();
}

当 A 调用 B 时,事务 Ta 与 Tb 会「相遇」,根据不同的处理手段分为不同的传播行为

事务级别 相关描述 理解
Required 支持当前事务,若当前不存在事务,则创建一个新事务。这是 Spring 使用的默认方式。 我要有一个事务
Supports 支持当前事务,若当前不存在事务,则以非事务方式执行。 很随意,有就有,没有就没有
Mandatory 支持当前事务, 若当前不存在事务,则抛出异常。 老子一定要有事务
Requires New 创建一个新事务,若当前存在事务,则挂起当前事务。 我要有一个新的事务
Not Supported 以非事务方式执行, 若当前存在事务,则挂起当前事务。 我不要有,要我也挂起来
Never 以非事务方式执行, 若当前存在事务,则抛出异常。 绝对绝对不要
Nested 创建一个新事务, 若当前存在事务,则将新创建的事务作为“子事务”,与已有事务构成父子关系。 有就是我儿子

Spring 中事务核心 API

Spring 中事务的管理核心 API 主要有三个:

  • PlatformTransactionManager:事务管理器,事务的主要执行者。

  • TransactionDefinition:事务属性定义。

    事务管理五个属性:


    • 传播行为
    • 隔离级别
    • 是否只读
    • 超时时间
    • 回滚规则
  • TransactionStatus:提供控制事务执行和查询事务状态的方法。

事务管理器 PlatformTransactionManagerTransactionDefinition 中获得事务的初始定义并开始执行,并将其状态保存到 TransactionStatus 中。

编程式事务管理

下面栗子都为 转账 demo 中代码,且省略导入 Jar 包步骤, 具体可查看项目中 build.gradle 依赖。

1. 配置事务管理器

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

2. 配置事务模板对象

将事务管理器注入到模板对象中,配置模板对象是为了简化代码编写。

<!-- 配置事务模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

3. 将事务模板对象注入到 Service 对象中,并进行使用(侵入)

  • 配置文件中代码:

  • Service 层代码:

这种方式对于代码的侵入性比较大,一般较少使用。

声明式事务管理

声明式事务相对于编程式事务的优点在于对代码的侵入性小,其本质是利用 AOP,在指定方法前面动态加入事务控制代码,执行完方法后自动提交或回滚。

「声明」即事务在配置文件中声明。

主要有四种方式

  • 基于 TransactionProxyFactoryBean 代理目标类方式

  • 基于拦截器的方式

  • 基于 Aspect AOP 的方式

  • 全注解方式

基于 TransactionProxyFactoryBean 的方式

这种方式的核心在于手动设置代理类,好处在于简单,但不够灵活。

<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 这里也可以直接注入 dataSource -->
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 2. 配置代理对象-->
<bean id="serviceProxy"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <!-- 注入目标对象 -->
    <property name="target" ref="service"/>

    <!-- 注入事务管理器 -->
    <property name="transactionManager" ref="transactionManager"/>

    <!-- 注入事务属性 -->
    <property name="transactionAttributes">
        <props>
            <prop key="transfer">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

<!-- ==================================== -->

<!-- 目标类 -->
<bean id="service" class="com.kbrx93.service.UserServiceImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- sessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>

<prop> 可选值:

  • propagation:传播行为

  • isolation:隔离级别

  • +Exception:设置不回滚的异常

  • read-only:是否为只读事务

  • -Exception:设置回滚的异常

这一种方式在使用的时候要注入代理类,而不是目标类。

这种方式也可以只定义一个代理基类,用 parent 属性引用即可。

...
<!-- 代理基类 -->
<bean id="serviceProxyBase"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
...
</bean>

<!-- 实际代理类 -->
<bean id="service" parent="serviceProxyBase">
    <!-- 注入目标对象 -->
    <property name="target" ref="service"/>
</bean>

基于拦截器的方式

在配置文件中定义事务拦截器,同时用 BeanNameAutoProxyCreator 将拦截器与指定方法绑定。

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />
    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>

<!-- 定义事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- 定义事务拦截器 -->
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager" />
    <!-- 配置事务属性 -->
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

<!-- 将拦截器与指定方法关联 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <!-- 指定方法 -->
    <property name="beanNames">
        <list>
            <value>*Service</value>
        </list>
    </property>
    
    <!-- 指定拦截器 -->
    <property name="interceptorNames">
        <list>
            <value>transactionInterceptor</value>
        </list>
    </property>
</bean>

基于 Aspect AOP 方式

这种方式的关键在于配置事务通知,然后用事务切面将方法与通知绑定,与上一种拦截器的方式思想相同。

  1. 配置事务管理器
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
  1. 配置事务通知及事务切面
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="transfer" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 配置事务切面 -->
<aop:config>
    <aop:pointcut id="pointcut"
                  expression="execution(* com.kbrx93.declarative.aspectj_xml.service.AccountService+.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>

<tx:attributes> 标签可选值:

  • propagation:传播行为

  • isolation:隔离级别

  • no-rollback-for:设置不回滚的异常(同上 +Exception)

  • read-only:是否为只读事务

  • rollback-for:设置回滚的异常(同上 -Exception)

关于 Spring AOP 及 Aspect 更多内容参见 Aspect Oriented Programming with Spring

全注解方式

1. 配置事务管理器

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

2. 开启事务注解驱动

<!-- 开启事务注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

3. 在对应的类或方法上打 @Transactional 标签,可通过标签内的值来设置事务的初始状态。

需要注意:@Transactional 只能应用到 public 方法才有效。
可在 透彻的掌握 Spring 中@transactional 的使用 查看更多关于 @Transactional 标签的属性及注意事项。

参考

Last Modified: July 5, 2018
Archives QR Code
QR Code for this page
Tipping QR Code