一、Spring中定义了5中不同的事务隔离级别
1. ISOLATION_DEFAULT(一般情况下使用这种配置既可)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
2. ISOLATION_READ_UNCOMMITTED
这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。(大部分数据库缺省的事物隔离级别都不会出现这种状况)
3. ISOLATION_READ_COMMITTED
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
什么是脏读?(修改且未提交引起)
例如:
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。与此同时,事务B正在读取张三的工资,读取到张三的工资为8000。随后,事务A发生异常,而回滚了事务。张三的工资又回滚为5000。最后,事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。 (大部分数据库缺省的事物隔离级别都不会出现这种状况)
4. ISOLATION_REPEATABLE_READ
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
什么是不可重复读?(修改引起)
例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,事务B把张三的工资改为8000,并提交了事务。随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。 (大部分数据库缺省的事物隔离级别都不会出现这种状况)
5. ISOLATION_SERIALIZABLE
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
什么是幻读?(添加新记录引起)
例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
(大部分数据库缺省的事物隔离级别都会出现这种状况,此种事物隔离级别将带来表级锁)
说明 :Oracle数据库缺省的事物隔离级别已经保证了避免脏读和不可重复读。但可能会幻读,避免幻读需要加表级锁,Oracle缺省行级锁。在基于Spring的事物配置中一定要慎重使用ISOLATION_SERIALIZABLE的事物隔离级别。这种配置会使用表级锁,对性能影响巨大。一般没有特殊需要的话,配置为使用数据库缺省的事物隔离级别便可。
二、Spring中定义了7中不同的事务传播属性
在使用Spring时,大部分会用到他的声明式事务,简单的在配置文件中进行一些规则配置,利用Spring的AOP功能就能轻松搞定事务问题;这里面就涉及到一个事务的传播属性问题Propagation,它在TransactionDefinition接口中定义,以供PlatfromTransactionManager使用,PlatfromTransactionManager是spring事务管理的核心接口。
TransactionDefinition
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
}
getTimeout()方法,它返回事务必须在多少秒内完成。
isReadOnly(),事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的。
getIsolationLevel()方法返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据。
在TransactionDefinition接口中定义了五个不同的事务隔离级别,ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应,ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
在TransactionDefinition接口中共有7种事务传播属性选项可用:
PROPAGATION_REQUIRED:
int PROPAGATION_REQUIRED = 0;
支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS:
int PROPAGATION_SUPPORTS = 1;
支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY:
int PROPAGATION_MANDATORY = 2;
支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW:
int PROPAGATION_REQUIRES_NEW = 3;
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED:
int PROPAGATION_NOT_SUPPORTED = 4;
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER:
int PROPAGATION_NEVER = 5;
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:
nt PROPAGATION_NESTED = 6;
支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。
现在结合一个实例,应用以上各种传播属性来进行说明:首先声明两个bean:ServiceA和ServiceB,其中ServiceB被引用;
ServiceA {
//事务属性配置为 PROPAGATION_REQUIRED
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
//事务属性配置为 PROPAGATION_REQUIRED
void methodB() {
}
}
接下来,我们就一一分析下:
1、PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务;比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候, ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA 的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。 这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被 提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。
2、PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行。
3、PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。
4、PROPAGATION_REQUIRES_NEW
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW, 那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后, 他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在 两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚, 如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5、PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED, 那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。 网管联盟bitsCN_com
6、PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER, 那么ServiceB.methodB就要抛出异常了。
7、PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立, 而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处也是他有一个savepoint:
ServiceA {
//事务属性配置为 PROPAGATION_REQUIRED
void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 级别
} catch (Exception e) {
ServiceC.methodC();
}
}
}
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如 ServiceC.methodC,继续执行,来尝试完成自己的事务;