原创作者: yiding_he   阅读:2749次   评论:2条   更新时间:2011-05-26    
事务的处理从来都是“三部曲”:
java 代码
  1. try {
  2.     begin();
  3.     // ...
  4.     commit();
  5. } catch (Exception e) {
  6.     rollback();
  7. }

在 JDBC 中,调用事务处理并不复杂,因为 Connection 类提供了现成的方法,就是 setAutoCommit() 、commit() 和 rollback()。

我们希望 DAO 能够在这个基础上更加方便的处理事务:每个线程都执行各自的事务,一旦事务开始,线程中所有的 DAO 对象的操作都将处于该事务当中,随同事务一起提交和回滚,这样就能大大简化当一个事务需要连接多个数据库时的处理方式。

每个线程执行各自的事务,这同数据库本身处理事务的方式是一致的,不需要特别设计。另外每个线程还需要有各自的标识说明当前事务状态,用 ThredLocal 就可以了。

首先给 DAO 类增加事务处理的静态方法:
DAO.java
  1. /**
  2. * 开始事务
  3. *
  4. * @throws DAOException 如果读取配置或执行数据库失败
  5. */
  6. public static void beginTransaction() throws DAOException {
  7.     try {
  8.         log.debug("事务开始。");
  9.         GlobalTransaction.begin();
  10.     } catch (Exception e) {
  11.         throw new DAOException(e);
  12.     }
  13. }
  14. /**
  15. * 提交事务
  16. *
  17. * @throws DAOException 如果执行数据库失败
  18. */
  19. public static void commitTransaction() throws DAOException {
  20.     // 如果不在事务当中则不做任何操作
  21.     if (!GlobalTransaction.isInTransaction()) {
  22.         return;
  23.     }
  24.     try {
  25.         GlobalTransaction.commit();
  26.     } catch (Exception e) {
  27.         throw new DAOException(e);
  28.     }
  29. }
  30. /**
  31. * 回滚事务
  32. *
  33. * @throws DAOException 如果回滚失败
  34. */
  35. public static void rollbackTransaction() throws DAOException {
  36.     // 如果不在事务当中则不做任何操作
  37.     if (!GlobalTransaction.isInTransaction()) {
  38.         return;
  39.     }
  40.     try {
  41.         GlobalTransaction.rollbackFinish();
  42.     } catch (Exception e) {
  43.         throw new DAOException(e);
  44.     }
  45. }

可以看出,这几个方法都允许连续调用多次,但实际上只有第一次起了作用。

我们争论过是否实现“事务嵌套”。但实际上根据事务的定义,事务不存在“嵌套”的说法。想一想,如果“子事务”被 rollback 掉了,那么因为它执行不成功,“父事务”也必然会被 rollback 掉,否则就不叫事务了。这跟在同一个事务里没有区别。JDBC 也没有所谓“事务嵌套”的概念。

当事务开始之后,持有 Connection 的 DbExecutor 对象必须被缓存起来以供下次调用。它们被缓存在 GlobalTransaction 中(见下图)。一旦事务结束,GlobalTransaction 便会将线程中缓存的 DbExecutor 对象全部清掉。



DbExecutorFactory.java
  1. /**
  2. * 创建 DbExecutor 对象
  3. *
  4. * @param dsName 数据源名称
  5. *
  6. * @return 连接到指定数据源的 DbExecutor 对象。如果当前处于事务中,则从事务缓存中获取 DbExecutor 对象,否则重新创建一个。
  7. *
  8. * @throws ConfigErrorException 如果读取配置失败
  9. * @throws DAOException 如果获取数据库连接失败
  10. */
  11. public DbExecutor getDbExecutor(String dsName) throws ConfigErrorException, DAOException {
  12.     try {
  13.         DbExecutor executor = GlobalTransaction.getExecutor(dsName);
  14.         if (executor == null || executor.isClosed()) {
  15.             log.debug("重新创建executor \"" + dsName + "\"");
  16.             executor = createNewExecutor(dsName);

  17.             if (GlobalTransaction.isInTransaction()) {
  18.             // 将获得的 DbExecutor 对象放入事务缓存
  19.                 executor.beginTransaction();
  20.                 GlobalTransaction.saveExecutor(dsName, executor);
  21.             }
  22.         }
  23.         return executor;
  24.     } catch (SQLException e) {
  25.         throw new DAOException(e);
  26.     }
  27. }
GlobalTransaction.java
 
  1. /** 
  2.  * 获得当前线程内缓存的连接到指定数据源的 DbExecutor 对象 
  3.  * 
  4.  * @param dsName 数据原名称 
  5.  * 
  6.  * @return 如果数据源处于事务当中,则返回正在处理该事务的 DbExecutor,否则返回 null 
  7.  */  
  8. public static DbExecutor getExecutor(String dsName) {  
  9.     HashMap map = getExecutorsMap();  
  10.     return (DbExecutor) map.get(dsName);  
  11. }  
  12.   
  13. /** 
  14.  * 开始事务 
  15.  */  
  16. public static void begin() {  
  17.     status.set(STARTED);  
  18. }  
  19.   
  20. /** 
  21.  * 提交事务 
  22.  */  
  23. public static void commit() {  
  24.     HashMap map = getExecutorsMap();  
  25.     Collection executors = map.values();  
  26.     for (Iterator iterator = executors.iterator(); iterator.hasNext();) {  
  27.         DbExecutor executor = (DbExecutor) iterator.next();  
  28.         executor.close();  
  29.     }  
  30.     map.clear();  
  31.     status.set(FINISHED);  
  32. }  
  33.   
  34. /** 
  35.  * 回滚事务 
  36.  */  
  37. public static void rollback() {  
  38.     if (!getStatus().equals(STARTED)) {  
  39.         return;  
  40.     }  
  41.   
  42.     Collection executors = ((HashMap) transactions.get()).values();  
  43.     for (Iterator iterator = executors.iterator(); iterator.hasNext();) {  
  44.         DbExecutor executor = (DbExecutor) iterator.next();  
  45.         try {  
  46.             executor.rollback();  
  47.         } catch (SQLException e) {  
  48.             log.error("事务回滚失败", e);  
  49.         }  
  50.     }  
  51.     getExecutorsMap().clear();  
  52.     status.set(ROLLBACKED);  
  53. }  


同时,DAO 对象在每次执行完数据库操作的时候,要判断一下当前是否处于事务当中。以 insert 方法为例:
DAO.java
  1. /**
  2. * 执行数据库插入
  3. *
  4. * @param obj 要插入的记录
  5. * @param tablename 要插入的表名
  6. *
  7. * @throws DAOException 如果读取配置或执行数据库失败
  8. */
  9. public void insert(Object obj, String tablename) throws DAOException {
  10.     DbExecutor executor = null;
  11.     try {
  12.         executor = initExecutor();
  13.         executor.insert(obj, tablename);
  14.     } catch (DAOException e) {
  15.          throw e;
  16.     } catch (Exception e) {
  17.          throw new DAOException(e);
  18.     } finally {
  19.         if (!GlobalTransaction.isInTransaction() && executor != null) {
  20.             executor.close();
  21.         }
  22.     }
  23. }


至此便实现了 DAO 对事务的处理。
评论 共 2 条 请登录后发表评论
2 楼 yiding_he 2010-06-22 20:49
figure_he_he 写道
commit 方法里为什么不是commit 而是executor.close();?


有兴趣的话可以打开这里:http://code.google.com/p/hydrogen-dao/

源码已公开。
1 楼 figure_he_he 2010-05-10 13:42
commit 方法里为什么不是commit 而是executor.close();?

发表评论

您还没有登录,请您登录后再发表评论

文章信息

Global site tag (gtag.js) - Google Analytics