Spring中的事务管理
spring
支持编程式事务和声明式事务
编程式事务管理 使用TransactionTemplate
或者直接使用底层的PlatformTransactionManager
。对于编程式事务管理,spring
推荐用用TransactionTemplate
。
声明式事务管理 建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于 @Transactional
注解的方式),便可以将事务规则应用到业务逻辑中
一般而言建议使用声明式事务,这样不会污染业务代码。与编程式事务不足在于粒度最细只能作用到方法级别,无法做到像编程式事务那样作用到代码块级别。
声明式事务管理
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于`@Transactional
注解。
使用xml配置文件
引入 aop 和 tx 的 命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx/
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd ">
定义事务管理器
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${db.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${db.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${db.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${db.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${db.maxWait}"></property>
</bean>
<!-- (事务管理)transaction manager ,需要注入一个数据源-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
定义事务Adivce
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" isolation="DEFAULT" propagation="NESTED"/>
</tx:attributes>
</tx:advice>
tx:method 属性
name
匹配的方法名称propagation
事务的传播行为isolation
事务隔离级别timeout
超时read-only
是否只读事务rollback-for
触发回滚的异常,逗号分隔no-rollback-for
不触发回滚的异常,逗号分隔
定义PonintCut
<aop:config >
<aop:pointcut id="daoOperation" expression="execution(* cn.echo0.dao.PersonMapper.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="daoOperation"/>
</aop:config>
使用@Transcation注解
注意事项
- 只有
@Transactional
注解应用到 public 方法,才能进行事务管理。 - 避免 Spring 的 AOP 的自调用问题
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有
@Transactional
注解的方法内部调用有@Transactional
注解的方法,有@Transactional
注解的方法的事务被忽略,不会发生回滚。
如:
@Service
public class OrderService {
private void insert() {
insertOrder();
}
@Transactional
public void insertOrder() {
//insert log info
//insertOrder
//updateAccount
}
}
insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。
上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。
为解决这两个问题,可以使用 AspectJ 取代 Spring AOP 代理。
需要将下面的 AspectJ 信息添加到 xml 配置信息中
<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.9</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
定义事务管理器
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.driverClassName}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${db.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${db.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${db.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${db.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${db.maxWait}"></property>
</bean>
<!-- (事务管理)transaction manager ,需要注入一个数据源-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
在配置文件中开启对注解事务的支持
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
在需要事务控制的方法或者类名上使用@Transcation注解
@Transactional(value = "transactionManager",propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public interface PersonMapper {
@Delete({
"delete from person",
"where id = #{id,jdbcType=INTEGER}"
})
int deleteByPrimaryKey(Integer id);
....
}
@Transcation 注解
package org.springframework.transaction.annotation;
....
public @interface Transactional {
String value() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
属性:
value
: 指定使用的TranscationManager
propagation
: 事务的传播行为isolation
: 事务隔离级别timeout
: 超时时间readOnly
: 是否只读事务rollbackFor
: 触发回滚的异常类对象数组rollbackForClassName
: 触发回滚的异常类名称数组noRollbackFor
: 不触发回滚的异常类对象数组noRollbackForClassName
: 不触发回滚的异常类名称数组
事务的属性
事务的隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
org.springframework.transaction.annotation.Isolation
枚举类中定义了五个表示隔离级别的值:
package org.springframework.transaction.annotation;
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
DEFAULT
:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED
。
READ_UNCOMMITTED
:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
READ_COMMITTED
:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
REPEATABLE_READ
:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
SERIALIZABLE
:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务的传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
org.springframework.transaction.annotation.Propagation
枚举类中定义了6个表示传播行为的枚举值:
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
REQUIRED
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。内部事务会影响外部事务。
SUPPORTS
:该如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
NOT_SUPPORTS
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
MANDATORY
:该如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。内部事务并不会影响外部的事务(savepoint)。
事务的超时时间
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务的只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化。默认为读写事务。
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。
事务的回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
Springboot 中的事务
在Spring Boot
中,当我们使用了spring-boot-starter-jdbc
或spring-boot-starter-data-jpa
依赖的时候,框架会自动默认分别注入DataSourceTransactionManager
或JpaTransactionManager
。所以我们不需要任何额外配置就可以用`@Transactional
注解进行事务的使用。
在项目中仅有一个数据源的情况下,是不需要指定事务管理器的,但是在项目中配置有多个数据源的情况下则需要通过@Transactional
的value
属性指定事务管理器