jdbc内嵌事务略(这里指savepoint相关)不同于pl/sql里写procture的内嵌事务概念(pl/sql里写procture的内嵌事务是完全独立的事务,相当于spring里的PROPAGATION_REQUIRES_NEW),
大家都知道spring的事务的传播属性:
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB CMT,
第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
现在来讨论大家关心的savepoint相关的PROPAGATION_NESTED,
这涉及到方法的嵌套调用:
假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下
/**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
// 调用 ServiceB 的方法
ServiceB.methodB();
}
那么如果 ServiceB 的 methodB 如果配置了事务, 配置为 PROPAGATION_NESTED 是什么意思呢?
ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下处理方式:
. 改写 ServiceA 如下
- ServiceA {
- /**
- * 事务属性配置为 PROPAGATION_REQUIRED
- */
- void methodA() {
- try {
- ServiceB.methodB();
- } catch (SomeException) {
- // 执行其他业务, 如 ServiceC.methodC(); 或者rollback(),或者commit()
- }
- }
- }
以下是我写的例子:
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public Session saveDepartment(Department user, User u, UserDao userDao) {
this.getJdbcTemplate().execute("update departments set name='"+user.getName()+"' where id="+user.getId()+"");
try {
userDao.saveUser(u);
} catch (Exception e) {
try {
conn.rollback(); 回滚处理,也可以提交
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
return null;
}
@Transactional(readOnly = false, propagation = Propagation.NESTED)
public void saveUser(User user) {
ConnectionHolder conHolder = (ConnectionHolder)
this.getJdbcTemplate().execute("update users set first_name='"+user.getName().getFirstName()+"' where id="+user.getId()+" ");
throw new RuntimeException("jimmy throw"); //人为抛出异常
}
调试信息如下:
Connection.getMetaData().supportsSavepoints() true
departments conn hashcode=8287698 departments conn is closed:false
saveUser conn hashcode=8287698 saveUser conn is closed:false
saveUser conn getAutoCommit=false
DataSourceTransactionManager:815 - Rolling back transaction to savepoint
可见:
两个方法用的一个连接,这还可以从oracle的v$session表验证,
并且B方法抛出异常后,事务回滚到保存点。
看连接v$session(这就是数据池里开的连接)
select ss.machine, Ss.Logon_Time from v$session ss; --where sid=143;
第三条就是我的机器的连接,整个测试时只有一条
看事务v$transaction
看被锁的表对象v$lock_object:
select All_Objects.Object_Name, rpad(oracle_username,10) o_name,session_id sid,
decode(locked_mode,0,'None',1,'Null',2,'Row share',
3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive') lock_type,
object_name ,xidusn,xidslot,xidsqn
from v$locked_object,all_objects
where v$locked_object.object_id=all_objects.object_id;
看用了什么锁v$lock:
select sid,type,trunc(id1/65536),mod(id1,65536),id2,
decode(lmode,0,'None',1,'Null',2,'Row share',
3,'Row Exclusive',4,'Share',5,'Share Row Exclusive',6,'Exclusive')
lock_type,request,ctime,block
from v$lock where TYPE IN('TX','TM');
结论:
当两个方法嵌套调用时候:,被调用的用NEST时,
用的是一个连接(v$session),一个事务(v$transaction)用三个lock(v$lock)锁两个表对象(v$lock_object)