Spring对事务的支持

首先我们要知道事务的实现是由数据库实现的,spring只是让我们更好的使用事务,如果你的数据库不支持事务,即使你在spring中使用了事务也不起作用。

一、spring的事务介绍

spring框架中对事务的支持有两种

  • 编程式事务管理
  • 声明式事务管理(推荐)

1.1 编程式事务

编程式事务管理:事务的相关操作完全由开发人员通过编码实现。所以编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。但是我们基本不推荐使用编程式事务。

下图展示的是使用原生jdbc对编程式事务的实现,完全有程序员来实现。

1
2
3
4
5
6
7
8
9
10
try {
connection.setAutoCommit(false);
empDao.executeInsert(user);
empDao.executeInsert(user2);
connection.commit();
}catch (Exception e){
connection.rollback();
}finally {
connection.close();
}

至于TransactionTemplate、PlatformTransactionManager 两个类的作用我们后续讲解。

1.2 申明式事务

声明式事务管理:事务的控制交给Spring框架来管理,开发人员只需要在Spring框架的配置文件中声明你需要的功能即可。

Spring中声明式事务管理的底层是基于AOP来完成的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。声明式事务它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。

1
2
3
4
5
6
7
8
9
try{
coon.setAutocommit(fase) ; => 使用前置通知
xxxxx => 本来的方法
coon.commit() => 返回通知
}catch(Throwable t){
coon.rollBack() => 异常通知
}finnally{
conn.release();=> 后置通知
}

二、spring事务的相关接口

Spring事务管理的相关接口有四个,如下:

  • PlatformTransactionManager:事务管理器,为不同的数据访问技术的事务提供不同的接口实现
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务的运行状态
  • TransactionAttribute: 事务属性,实现了对回滚规则的扩展

Spring的事务机制是用统一的机制来处理不同数据访问技术的事务处理,Spring并不直接管理事务,而是提供了多种事务管理器。Spring的事务机制提供了一个org.springframework.transaction.PlatformTransactionManager接口,将事务管理的职责委托给JDBC或者Hibernate等持久化机制所提供的相关平台框架的事务来实现。通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,其具体的实现就是各个平台自己的事情了,对应的相关实现如下表所示。

数据库访问技术 实现
JDBC DataSourceTransactionManager
JPA JpaTransactionManager
Hibernate HibernateJpaTransactionManager
JDO JdoTransactionManager
分布式事务 JtaTransactionManager

三、基于注解的声明式事务

在Spring中使用声明式事务一般会使用注解来实现,即@Transactional注解,该注解可以使用在类、接口和方法上:

  • 作用在类:表示所有该类的 public 方法都配置相同的事务属性信息。
  • 作用在方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效。

需要特别注意的是,此@Transactional注解来自org.springframework.transaction.annotation包,而不是javax.transaction。

@Transactional注解中常用参数:

  • value:当在配置文件中有多个 TransactionManager,可以用该属性指定选择哪个事务管理器。
  • propagation:事务的传播行为,默认值为 REQUIRED
  • isolation:事务的隔离级别,默认值为 DEFAULT,即采用数据库的默认隔离级别。
  • timeout:事务的超时时间(单位是秒),默认值为 -1。如果超过该时间限制但事务还未提交,则自动回滚事务。
  • readOnly:用于指定事务是否为只读事务,默认值为 false。为了忽略那些不需要事务的方法,比如select读取数据,可以设置readOnly = true。
  • rollbackFor:指定能够触发事务回滚的异常类型,可以指定多个异常类型。
  • noRollbackFor:指定不用回滚事务的异常类型,可以指定多个异常类型。

四、事务的传播行为

事务的传播机制一般用在事务的嵌套中,当事务方法被另一个事务方法调用时,则应该指定事务如何传播。比如事务方法A直接或间接调用了方法B,那么这两个方法是各自作为独立的方法提交,还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

注:事务的传播行为和隔离级别都定义在TransactionDefinition接口中:

1
2
3
4
5
6
7
8
9
10
11
12
13
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;

int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;

事务的传播行为如下表所示(主要学习前两个即可,其它的简单了解):

事务传播行为 描述
PROPAGATION_REQUIRED 支持外层事务。这是Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,则创建一个新的事务。
PROPAGATION_REQUIRES_NEW 不支持外层事务。该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可。
PROPAGATION_SUPPORTS 支持外层事务。如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORTED 不支持外层事务。该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER 不支持外层事务。该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY 支持外层事务。与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED Spring 所特有的。该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚,等价于TransactionDefinition.PROPAGATION_REQUIRED。

五、事务的只读属性和超时属性

①、只读属性

一个事务如果是做查询操作,可以设置为只读,此时数据库可以针对查询操作来做优化,有利于提高性能。

1
2
3
4
@Transactional(readOnly = true)
public void doSomething() {
//...相关操作
}

如果是针对增删改方法设置只读属性,则会抛出下面异常:

1
2
3
表面的异常信息:TransientDataAccessResourceException: PreparedStatementCallback

根本原因:SQLException: Connection is read-only. Queries leading to data modification are not allowed(连接是只读的。查询导向数据的修改是不允许的。)

实际开发时建议把查询操作设置为只读。

②、超时属性

一个数据库操作有可能因为网络或死锁等问题卡住很长时间,从而导致数据库连接等资源一直处于被占用的状态。所以我们可以设置一个超时属性,让一个事务执行太长时间后,主动回滚。事务结束后把资源释放出来。

1
2
3
4
@Transactional(timeout = 60) //单位为秒
public void doSomething() {
//...相关操作
}