跳到主要内容

18. Spring声明式事务

回顾事务:

  • 事务:要么都成功,要么都失败
  • 事务在项目开发中十分重要,涉及到数据的一致性问题
  • 确保数据完整性和一致性

事务的ACID的原则:

  • A:原子性
  • C:一致性
  • I:隔离性:多个业务操作同一个资源时,防止数据损坏
  • D:持久性:事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化到存储中

事务:

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 DataSourceTransactionManager 来实现事务管理。

一旦配置好了 Spring 的事务管理器,你就可以在 Spring 中按你平时的方式来配置事务。并且支持 @Transactional 注解和 AOP 风格的配置。在事务处理期间,一个单独的 SqlSession 对象将会被创建和使用。当事务完成时,这个 session 会以合适的方式提交或回滚。

事务配置好了以后,MyBatis-Spring 将会透明地管理事务。这样在你的 DAO 类中就不需要额外的代码了。

Spring中的事务分两类:

  • 编程式事务:需要在代码中进行事务管理
  • 声明式事务:AOP

标准配置:

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

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

传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource

注意:为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。

通过AOP织入事务:

<!--结合AOP实现事务的织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给指定方法配置事务-->
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<!--或者使用*通配符匹配所有方法-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!--配置事务切面-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.neu.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!--启用基于类的代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

默认情况下,代理被创建都是基于接口的,但有些情况会出现类型注入错误的问题,那么就开启基于类创建:<aop:aspectj-autoproxy proxy-target-class="true"/>

注解配置:

在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象,之后开启注解支持:

<tx:annotation-driven/>

声明式事务可以直接使用@Transactional注解来配置,看一下@Transactional注解都有啥:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 别名为transactionManager,其实这两是同一个。就是事务的名字。
@AliasFor("transactionManager")
String value() default "";
// 事务的传播行为,默认值为REQUIRED
Propagation propagation() default Propagation.REQUIRED;
// 事务的隔离级别,默认为默认值(也就是使用底层数据库的隔离级别)
Isolation isolation() default Isolation.DEFAULT;
// 超时时间,默认为 -1,也就是没有超时时间。
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只读,默认为false。
boolean readOnly() default false;
// 会触发事务回滚的异常的字节码数组
Class<? extends Throwable>[] rollbackFor() default {};
// 不会触发事务回滚的异常的字节码数组
Class<? extends Throwable>[] noRollbackFor() default {};
}
复制代码

这里直接在实现类上使用,这个注解还可以在接口(一般不在接口上使用)和方法上使用(可见性必须为public,否则会被忽略)。

事务的隔离级别:

通过org.springframework.transaction.annotation.Isolation 这个枚举类来指定。

public enum Isolation {
// 这是默认值,表示使用底层数据库的默认隔离级别。对于MySQL的InnoDB存储引擎,它是REPEATABLE_READ,其它一般的都是READ_COMMITTED
DEFAULT(-1),
// 跟没有一样,几乎不使用。
READ_UNCOMMITTED(1),
// 只能读取另一个事务已提交的事务,能防止脏读。也是一般数据库的默认隔离级别。
READ_COMMITTED(2),
// 可重复读(在一个事务内多次查询的结果相同,其它事务不可修改该查询条件范围内的数据,相当于加了个读锁)
REPEATABLE_READ(4),
// 所有的事务依次逐个执行,相当于串行化了,效率太低,一般也不使用。
SERIALIZABLE(8);
}

事务的传播行为:

如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。org.springframework.transaction.annotation.Propagation枚举类中定义了7表示传播行为的枚举值。

public enum Propagation {

// 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
REQUIRED(0),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
SUPPORTS(1),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
MANDATORY(2),
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
REQUIRES_NEW(3),
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED(4),
// 以非事务方式运行,如果当前存在事务,则抛出异常。
NEVER(5),
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
NESTED(6);
}