使用spring管理事务

在实际开发中我们通常使用spring来帮助管理事务方面的事情,因此事务相关的处理都交由spring了。在spring中支持xml和注解的方式进行事务配置。
事务的配置通常是在service层,用来保证业务逻辑上数据的原子性。因为在service层有可能会调用多个dao中的方法操作数据库,这些方法的操作就需要事务来保证其一致性。
spring对于事务的管理分为两种:

  • 编程式事务

    通过编写代码来管理事务,类似于jdbc里面的通过代码来对事务进行提交或者回滚。

    优点:事务最细力度可以做到代码块级别。

    缺点:会污染业务代码。

  • 声明式事务

    通过使用xml配置文件或者注解的方式来管理事务,无需编写代码,原理是使用aop进行事务的管理。

    优点:不会污染业务代码,通过配置文件或注解即可完成事务的管理

    缺点:事务最细力度只能做到方法级别。

spring事务管理相关API

spring中有一个PlatformTransactionManager接口,该接口叫做事务管理器接口,我们会使用它的两个实现类来完成事务的控制:

  • DataSourceTransactionManager:使用 JDBC 或 myBatis 进行持久化数据时使用。
  • HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

Spring 事务的默认回滚方式是:发生运行时异常时回滚,发生一般性异常时提交。不过,对于一般性异常,我们也可以手工设置其回滚方式。
复习一下异常方面的知识:

  • 运行时异常:程序在运行时才会出现的异常,是RuntimeException的子类,例如NullPointerException空指针异常。
  • 一般性异常:即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译,例如IOException。

五个事务隔离级别常量

这些常量均是以 ISOLATION开头。例如 ISOLATIONREPEAT ABLE_READ。

  • DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLEREAD;Oracle默认为 READCOMMITTED。
  • READ_UNCOMMITTED:读未提交。未解决任何问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。解决脏读、不可重复读,幻读的问题,效率低。

七个事务传播行为常量

事务的传播行为指的是处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,例如 PROPAGATION_REQUIRED。

  • REQUIRED

    指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事
    务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。
    如该传播行为(REQUIRED)加在 doOther()方法上。倘若doSome()方法在调用 doOther()方法时就是在事
    务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用
    doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

  • SUPPORTS

    指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

  • MANDATORY

    指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。

  • REQUIRES_NEW

    总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

  • NOT_SUPPORTED

    指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。

  • NEVER

    指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。

  • NESTED

    指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。

默认事务超时时限

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,及不支持事务超时时限设置的 none 值。
注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可,默认值是-1。

使用xml配置事务

配置事务管理器和数据源,数据源使用在spring-mybatis.xml文件中的配置的内容。

 <!-- 事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

事务传播行为,事务的传播使用REQUIRED,name表示要添加事务的方法,其中*表示通配符。

    <!-- 事务传播行为 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="create*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>

配置切面,这里的切入点要注意是在service层

    <!-- 切面 -->
    <aop:config>
        <!--切入点必须是在service层-->
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.monkey1024.service.*.*(..))" />
    </aop:config>
</beans>

使用注解配置事务

spring还支持注解的方式来配置事务,此时只需要在spring-tx.xml配置文件中添加下面内容:

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

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

然后使用@Transactional 注解即可,该注解可以用于类上,也可以用于方法上,需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。

@Transactional中的属性如下:

  • propagation :用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
  • isolation : 用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
  • readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
  • timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
  • rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • rollbackForClassName: 指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
  • noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。

添加在类名上:

@Transactional
public class UserServiceImpl implements UserService {}

用在方法上,表示当抛出空指针异常时会进行回滚:

 @Transactional(propagation = Propagation.REQUIRED,rollbackFor = NullPointerException.class)
public void addUser(User user) throws Exception {
    userDao.addUser(user);
}