事务管理
基于JDBC 的事务管理机制
ibatis 提供了自动化的JDBC 事务管理机制。
对于传统JDBC Connection 而言,我们获取Connection 实例之后,需要调用Connection.setAutoCommit 设定事务提交模式。在AutoCommit 为true 的情况下,JDBC 会对我们的操作进行自动提交,此时,每个JDBC 操作都是一个独立的任务。为了实现整体事务的原子性,我们需要将AutoCommit 设为false ,并结合Connection.commit/rollback 方法进行事务的提交/回滚操作。
ibatis 的所谓“自动化的事务提交机制”,即ibatis 会根据当前的调用环境,自动判断操作是否需要自动提交。
如果代码没有显式的调用SqlMapClient.startTransaction() 方法,则ibatis会将当前的数据库操作视为自动提交模式(AutoCommit=true),如:
sqlMap = xmlBuilder.buildSqlMap(reader);
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
在执行的时候,会自动判定运行环境,这里操作sqlMap.update ibatis 当前的update并没有相对应的事务范围(startTransaction 和endTransaction 代码块),于是ibatis 将其作为一个单独的事务,并自动提交。对于上面的代码,update 执行了两次,与其相对应,事务也提交了两次(即每个update 操作为一个单独的事务)。不过,值得注意的是,这里的所谓“自动判定”,可能有些误导,ibatis 并没有去检查当前是否已经有事务开启,从而判断当前数据库连接是否设定为自动提交。实际上,在执行update 语句时,sqlMap 会检查当前的Session 是否已经关联了某个数据库连接,如果没有,则取一个数据库连接,将其AutoCommit 属性设为true ,然后执行update 操作,执行完之后又将这个连接释放。这样,上面两次update 操作实际上先后获取了两个数据库连接,而不是我们通常所认为的两次update 操作都基于同一个JDBC Connection 。这点在开发时需特别注意。
对于多条SQL 组合而成的一个JDBC 事务操作而言,必须使用
startTransaction、commit 和endTransaction 操作以实现整体事务的原子性。
如:
try{
sqlMap = xmlBuilder.buildSqlMap(reader);
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
sqlMap.commitTransaction();
}finally{
sqlMap.endTransaction();
}
如果user1 或者user2 的update 操作失败,整个事务就会在endTransaction 时回滚,从而保证了两次update 操作的原子性。
基于JTA 的事务管理机制
JTA 提供了跨数据库连接(或其他JTA 资源)的事务管理能力。这一点是与JDBC Transaction 最大的差异。
JDBC 事务由Connnection 管理,也就是说,事务管理实际上是在JDBC Connection 中实现。事务周期限于Connection 的生命周期。同样,对于基于JDBC 的ibatis 事务管理机制而言,事务管理在SqlMapClient 所依托的JDBC Connection 中实现,事务周期限于SqlMapClient 的生命周期。
JTA 事务管理则由JTA 容器实现,JTA 容器对当前加入事务的众多Connection 进行调度,实现其事务性要求。JTA 的事务周期可横跨多个JDBC Connection 生命周期。
同样,对于基于JTA 事务的ibatis 而言,JTA 事务横跨可横跨多个SqlMapClient 。
下面这幅图形象的说明了这个问题:
为了在ibatis 中使用JTA 事务管理,我们需要在配置文件中加以设定:
……
在实际开发中,我们可能需要面对分布式事务的处理,如系统范围内包含了多个数据库,
也许还引入了JMS 上的事务管理(这在EAI 系统实现中非常常见)。我们就需要引入JTA
以实现系统范围内的全局事务,如下面示例中,我们同时将user 对象更新到两个不同的数
据库:
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
try{
sqlMap1.startTransaction();
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
sqlMap1. commitTransaction();
}finally{
sqlMap1.endTransaction();
}
上面的代码中,两个针对不同数据库的实例,在同一个事务中SqlMapClient JTA
对user 对象所对应的数据库记录进行更新。外层的sqlMap1 启动了一个全局事务,此
事务将涵盖本线程内commitTransaction 之前的所有数据库操作。只要其间发生了
异常,则整个事务都将被回滚。
外部事务管理
基于JTA 的事务管理还有另外一个特殊情况,就是利用外部事务管理机制。
对于外部事务管理,我们需要在配置文件中进行如下设定:
……
下面是一个外部事务管理的典型示例:
UserTransaction tx = new InitialContext().lookup(“……”);
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
tx.commit();
此时,我们借助实例启动了一个全局事务。之后的操作JTA UserTransaction ibatis
( sqlMap1.update 和sqlMap2.update)全部被包含在此全局事务之中,当
UserTransaction 提交的时候,
ibatis 操作被包含在事务中提交,反之,如果UserTransaction
回滚,那么其间的ibatis 操作也将作为事务的一部分被回滚。这样,我们就实现了ibatis
外部的事务控制。
另一种外部事务管理方式是借助EJB 容器,通过EJB 的部署配置,我们可以指定
EJB 方法的事务性
下面是一个Session Bean 的doUpdate 方法,它的事务属性被申明为“Required”,
EJB 容器将自动维护此方法执行过程中的事务:
/**
*
@ejb.interface-method
*
view-type="remote"
*
*
@ejb.transaction type = "Required"
**/
public void doUpdate(){
//EJB环境中,通过部署配置即可实现事务申明,而无需显式调用事务
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
}//方法结束时,如果没有异常发生,则事务由EJB容器自动提交。
上面的示例中,ibatis 数据操作的事务管理将全部委托给EJB 容器管理,由EJB
容器控制其事务调度。
在上面JTA 事务管理的例子中,为了保持清晰,我们省略了startTransaction 、
commitTransaction 和endTransaction 的编写,在这种情况下,调用ibatis
的事务管理方法并非必须,不过在实际开发时,请酌情添加startTransaction 、
commitTransaction 和endTransaction 语句,这样可以获得更好的性能(如果
省略了startTransaction 、commitTransaction 和endTransaction 语句,
ibatis 将为每个数据操作获取一个数据连接,就算引入了数据库连接池机制,这样的
无谓开销也应尽量避免,具体请参见JDBC 事务管理中的描述),并保持代码风格的统
一。
ibatis默认自动提交事务,在使用Dao时,类似如下代码:
Integer authorId=xxxDao.insert(author);
...
book.setAuthorIds(authorId);
xxxDao.insert(book);
因为没有显式地启动事务,ibatis会认为两个insert为两个事务。
可以通过ibatis中的SqlMapClient显示地进行事务管理。
开始事务:sqlMapClient.startTransaction();
提交事务:sqlMapClient.commitTransaction();
结束事务:sqlMapClient.endTransaction();
回滚事务:sqlMapClient.endTransaction();
try{
sqlMapClient.startTransaction();
Integer authorId=xxxDao.insert(author);
...
book.setAuthorIds(authorId);
xxxDao.insert(book);
sqlMapClient.commitTransaction();
}finally{
sqlMapClient.endTransaction();
}
事务不能嵌套,在调用commitTransaction或endTransaction方法之前,从同一线程多次调用startTransaction将抛出异常。也就是说,对于每个SqlMap实例,每个线程最多只能打开一个事务。