说起事务,大家应该多多少少用过,尤其是在一个service方法中调用多次dao操作,我们一定要用到事务(
@Transational注解
),那么这个事务的默认隔离级别和传播机制是什么呢?
先来讲讲 脏读
不可重复读
和 幻读
。
-
脏读:我们在并发编程中是很熟悉的,通俗的讲就是你读得数据已经被修改了,已经过时失去意义了。
-
不可重复读: 同一个事务里面多次读取同一行数据,却返回不同的结果。
-
幻读:同样一笔查询在整个事务过程中多次执行后,查询所得的结果集不一样。
事务四大特性 ACID
1. 原子性 (Atomicity)
要求事务所包含的全部操作是一个不可分割的整体,如果有一步发生异常,则全部不提交。
2. 一致性 (Consistency)
A给B转钱,A减和B增这两个操作必须保持一致。
3. 隔离性 (Isolation)
事务会将一部分数据与其他事务隔离,防止脏读等。
4. 持久性 (Durability)
事务的结果被写到持久化存储器中。
事务四大隔离级别
隔离级别越高,则性能相对越低,反之亦然。
1. Read Uncommitted
最低的隔离级别,跟你直译的意思一样:可以读取其它事务未完成的结果。(脏读)
很明显, 脏读
不可重复读
和 幻读
这三个问题它都有。
2. Read Committed
大部分数据库采用的默认隔离级别,比上一个隔离级别多了限定:在该事务完成后,才能读取该事务的数据更新后的结果。
它可以避免脏读,但是也有不可重复读取和幻读的问题。
3. Repeatable Read
可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数 据的更新提交与否。
Repeatable Read隔离级别避免了脏读和不可重复读取的问题,但无法避免幻读。
4. Serializable
最为严格的隔离级别,所有的事务操作都必须依次顺序执行,可以避免其他隔离级别遇到的所有问题,是最为安全的隔离级别, 但同时也是性能最差的隔离级别。
通常情况下,我们会使用其他隔离级别加上相应的并发锁的机制来控制对数据的访问,这样既保证 了系统性能不会损失太大,也能够一定程度上保证数据的一致性。
Spring事务传播机制
从JDBC的事务说起
我们都知道,JDBC给我们提供了事务。
-
try{
-
con.setAutoCommit(false);//开启事务
-
......
-
con.commit();//try的最后提交事务
-
} catch() {
-
con.rollback();//回滚事务
-
}
获取事务隔离级别
-
Connection.getTransactionIsolation()
设置事务隔离级别
-
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Spring事务机制
Spring并不会直接管理事务,而是提供了事务管理器,将事务管理的职责委托给JPA JDBC JTA DataSourceTransaction JMSTransactionManager 等框架提供的事务来实现。
那么,Spring提供的事务管理器是什么呢?
是 PlatformTransactionManager.java
接口:
PlatformTransactionManager.java
Spring提供的事务管理器。不同的事务遵循该事务管理器的API,便能很轻松的交给Spring管理。
-
public interface PlatformTransactionManager {
-
// 通过Transation定义 获取Transation
-
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
-
// 提交事务
-
void commit(TransactionStatus var1) throws TransactionException;
-
// 回滚事务
-
void rollback(TransactionStatus var1) throws TransactionException;
-
}
可以看到它里面引用到了 TransactionDefinition
和 TransactionStatus
.
TransactionDefinition.java
它里面包含了事务的定义。
-
public interface TransactionDefinition {
-
// 传播机制
-
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;
-
int ISOLATION_READ_COMMITTED = 2;
-
int ISOLATION_REPEATABLE_READ = 4;
-
int ISOLATION_SERIALIZABLE = 8;
-
int TIMEOUT_DEFAULT = -1;
-
-
int getPropagationBehavior();
-
// 获取隔离级别
-
int getIsolationLevel();
-
-
int getTimeout();
-
-
boolean isReadOnly();
-
-
@Nullable
-
String getName();
-
}
TransactionStatus.java
事务的状态。
-
public interface TransactionStatus extends SavepointManager, Flushable {
-
boolean isNewTransaction();
-
-
boolean hasSavepoint();
-
-
void setRollbackOnly();
-
-
boolean isRollbackOnly();
-
-
void flush();
-
-
boolean isCompleted();
-
}
Spring默认事务使用
1. 代码方式使用
-
@Autowired
-
private PlatformTransactionManager transactionManager;
-
public void testTX(){
-
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
-
TransactionStatus status = transactionManager.getTransaction(definition);
-
try {
-
// 业务逻辑
-
// ...
-
-
// 提交事务
-
transactionManager.commit(status);
-
}catch (Exception e){
-
// 发生异常,事务回滚
-
transactionManager.rollback(status);
-
}
-
}
2. 注解方式使用
-
@Transactional
-
void testTX2(){
-
// 业务逻辑 ...
-
}
这不是玄学,它的底层是依靠AOP动态代理实现,其实重新渲染出的代码和第一个使用方式类似,不过大大减少了开发复杂度。
扩展:@Transactional注解
-
@Target({ElementType.METHOD, ElementType.TYPE})
-
@Retention(RetentionPolicy.RUNTIME)
-
@Inherited
-
@Documented
-
public @interface Transactional {
-
-
//指定使用的事务管理器
-
@AliasFor("transactionManager")
-
String value() default "";
-
-
@AliasFor("value")
-
String transactionManager() 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 {};
-
}
Spring事务实践
非入门选手下面的demo可能会引起你的不适(浪费时间)。 假设我要完成一个功能,当删除用户的时候,将与该用户有关的所有数据行都删除。
-
public void delUser(Integer userId) {
-
// 删除和用户相关的信息
-
otherRepository.deleteByUserId(userId);
-
// 删除用户
-
userRepository.deleteById(userId);
-
}
这样的写法一般来讲,会成功的完成任务。但是如果这样一段代码:
-
public void delUser(Integer userId) {
-
// 删除和用户相关的信息
-
otherRepository.deleteByUserId();
-
if (true) {
-
throw new RuntimeException("xxx");
-
}
-
// 删除用户
-
userRepository.deleteById(userId);
-
}
结果会是: deleteByUserId()
执行成功, deleteById()
执行失败,不满足数据的一致性。
所以我们需要事务来限制:要么全部执行,要么全部不执行(方法中有异常就自动回滚)。那怎么实现呢,只需要在方法上加一个注解: @Transactional
-
@Transactional
-
public void delUser(Integer userId) {
-
// 删除和用户相关的信息
-
otherRepository.deleteByUserId();
-
if (true) {
-
throw new RuntimeException("xxx");
-
}
-
// 删除用户
-
userRepository.deleteById(userId);
-
}
Spring 加载第三方事务管理
比如我有个需求(接着上次的强票系统II),要求信息不能丢失,要用到RabbitMQ的事务管理,那怎么去加载到Spring的事务管理器中呢?
-
@Bean
-
public ConnectionFactory connectionFactory() {
-
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
-
return connectionFactory;
-
}
-
-
@Bean
-
public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
-
return new RabbitTransactionManager(connectionFactory);
-
}
我们只需要这样做便可以使的使用 @Transactional注解
来实现对RabbitMQ的事务管理,其它框架也类似。
@Transactional注解
概述
事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
Spring Framework对事务管理提供了一致的抽象,其特点如下:
- 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
- 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用
- 提供比其他事务API如JTA更简单的编程式事务管理API
- 与spring数据访问抽象的完美集成
事务管理方式
spring支持编程式事务管理和声明式事务管理两种方式。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。
自动提交(AutoCommit)与连接关闭时的是否自动提交
自动提交
默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果
执行失败则隐式的回滚事务。
对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。不过,这个我们不用担心,spring会将底层连接的自动提交特性设置为false。
org/springframework/jdbc/datasource/DataSourceTransactionManager.java
1 // switch to manual commit if necessary. this is very expensive in some jdbc drivers, 2 // so we don't want to do it unnecessarily (for example if we've explicitly 3 // configured the connection pool to set it already). 4 if (con.getautocommit()) { 5 txobject.setmustrestoreautocommit(true); 6 if (logger.isdebugenabled()) { 7 logger.debug("switching jdbc connection [" + con + "] to manual commit"); 8 } 9 con.setautocommit(false); 10 }
有些数据连接池提供了关闭事务自动提交的设置,最好在设置连接池时就将其关闭。但C3P0没有提供这一特性,只能依靠spring来设置。
因为JDBC规范规定,当连接对象建立时应该处于自动提交模式,这是跨DBMS的缺省值,如果需要,必须显式的关闭自动提交。C3P0遵守这一规范,让客户代码来显式的设置需要的提交模式。
连接关闭时的是否自动提交
当一个连接关闭时,如果有未提交的事务应该如何处理?JDBC规范没有提及,C3P0默认的策略是回滚任何未提交的事务。这是一个正确的策略,但JDBC驱动提供商之间对此问题并没有达成一致。
C3P0的autoCommitOnClose属性默认是false,没有十分必要不要动它。或者可以显式的设置此属性为false,这样会更明确。
基于注解的声明式事务管理配置
spring-servlet.xml
1 <!-- transaction support--> 2 <!-- PlatformTransactionMnager --> 3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 4 <property name="dataSource" ref="dataSource" /> 5 </bean> 6 <!-- enable transaction annotation support --> 7 <tx:annotation-driven transaction-manager="txManager" />
还要在spring-servlet.xml中添加tx名字空间
1 ... 2 xmlns:tx="http://www.springframework.org/schema/tx" 3 xmlns:aop="http://www.springframework.org/schema/aop" 4 xsi:schemaLocation=" 5 ... 6 7 http://www.springframework.org/schema/tx 8 9 10 http://www.springframework.org/schema/tx/spring-tx.xsd 11 12 ...
MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
另外需要下载依赖包aopalliance.jar放置到WEB-INF/lib目录下。否则spring初始化时会报异常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
spring事务特性
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
1 public interface PlatformTransactionManager { 2 3 TransactionStatus getTransaction(TransactionDefinition definition) 4 throws TransactionException; 5 6 void commit(TransactionStatus status) throws TransactionException; 7 8 void rollback(TransactionStatus status) throws TransactionException; 9 }
其中TransactionDefinition接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。
spring事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。
还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
@Transactional注解
@Transactional属性
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
用法
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
1 @Transactional(readOnly = true) 2 public class DefaultFooService implements FooService { 3 4 public Foo getFoo(String fooName) { 5 // do something 6 } 7 8 // these settings have precedence for this method 9 //方法上注解属性会覆盖类注解上的相同属性 10 @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) 11 public void updateFoo(Foo foo) { 12 // do something 13 } 14 }