论坛首页 Java企业应用论坛

jdbc还是ibatis?

浏览 50534 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-02-06  
公司的一个大系统的持久层一直是直接使用jdbc。在jdbc的基础上,又自制了一个简陋的cache。

每个持久功能的实现都比较类似,大致相当于这样:
MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
try {
  PreparedStatement stmt = conn.getPreparedStatement("some statement id, identifying a sql statement in an xml file");
  stmt.setString(1, "param 1");
  stmt.setInt(2, param2);
  ...
  try {
    ResultSet resultSet = stmt.executeQuery();
    try{
      while(resultSet.next()) {
        ...
      }
    }
    finally {
      resultSet.close();
    }
  }
  finally {
    stmt.close();
  }
}
finally {
  ConnectionManager.checkIn(conn);
}

当然,各个功能的实现不完全一样,有的有事务,有的没有;有的忘了关闭statement,有的忘了checkIn connection;有的在出现Error的时候忘了rollback。等等等等。

dao层的代码就是调用这些不同的jdbc代码,然后再包上一层HashMap做cache:
Object cacheKey = ...;
synchronized(cache) {
  Account acct = (Account)cache.get(cacheKey);
  if(acct == null) {
    acct = runJdbcForAccount(...);
    cache.put(cacheKey, acct);
  }
  return acct.cloneAccount();
}

当然,还要自己实现cloneAccount()。
所有对Account, Contribution, Plan之类的cache代码也类似。

后来鉴于偶尔出现资源泄漏问题,一个程序员写了一个jdbc模板,长成这个样子:
abstract class PersisterCommand {
  protected abstract void populateStatement(PreparedStatement stmt);
  protected abstract Object processResult(ResultSet resultSet);
  protected abstract boolean isTransactional();
  protected abstract PreparedStatement getStatement();
  public Object run() {
    MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
    try {
      PreparedStatement stmt = getStatement();
      populateStatement(stmt);
      ...
      try {
        if(isTransactional()) {
          conn.startTransaction();
        }
        ResultSet resultSet = stmt.executeQuery();
        try{
          Object result = processResult(resultSet);
          if(isTransactional()) {
            conn.commitTransaction();
          }
          return result;
        }
        catch(Exception e){
          if(isTransactional()) conn.rollbackTransaction();
          throw e;
        }
        finally {
          resultSet.close();
        }
      }
      finally {
        stmt.close();
      }
    }
    finally {
      ConnectionManager.checkIn(conn);
    }
  }
}

然后上面的代码可以简化为仅仅重载这四个抽象函数:
getStatement负责取得某个特定的sql statement;populateStatement负责填充参数;processResult负责把ResultSet转换成domain object;isTransactional制定是否使用事务。

介绍了这么多背景情况,希望你已经看到了,原来的直接jdbc方法是非常繁琐,容易出错,代码量大,而且重复很多。
这个PersisterCommand也有很多局限:
1。它只能处理一个connection一个statement,不能做batch。
2。它在Error出现的时候没有rollback。
3。子类仍然要针对jdbc api写一些有重复味道的代码。
4。代码不容易单元测试。因为ConnectionManager.checkOut()和ConnectionManager.checkIn()都是写死的。


另外,这个自制的cache也是一个重复代码的生产者。

针对这种情况,我本来想继续重构,弄出一个CacheManager和更灵活的jdbc模板。但是后来一想,倒还不如直接用ibatis来得好。毕竟ibatis已经是被业界广泛使用的工具,总比自己制造轮子强。而且,相比于hibernate,ibatis也有更贴近我们现在的模型和使用习惯的优势。


我的一个同事(公司的元老),开始是对ibatis很感兴趣的。

可惜的是,当我完成了ibatis的集成,他试用了一下之后就改变了主意。这个同事在项目组甚至整个公司说话都是很有分量的,不说服他,推广ibatis就面临夭折的可能。

我的ibatis的集成长成这个样子:
public interface Action {
  Object run(SqlMapSession session);
}
public class IbatisPersistence {
  public SqlMapSession openSession();
  public Object queryForObject(String key);
  public List queryForList(String key);
  public int update(String key, boolean useTransaction);
  public int delete(String key, boolean useTransaction);
  public int update(String key);
  public int delete(String key);
  public Object run(Action action);
}


这样,除非用户代码调用openSession(),其它的函数都自动处理了Session的关闭。事务处理用一个boolean参数来控制也相当简单。

上面的那么多jdbc代码和cache代码最终就可以直接变成:
Accunt acct = persistence.queryForObject("getAccountById", accountId);


那么同事对这个东西的意见在哪里呢?
1。他和另外一个同事为调试一个使用了ibatis的程序bug花了一天时间。后来把ibatis删掉,直接用jdbc就修好了。
当时我在休假,回来后一看,这个bug首先是一个stored proc的bug。他们花很多时间在ibatis里面找问题其实都是瞎耽误工夫;其次,在他们到处找ibatis的问题的时候,注释掉了两行关键代码,后来忘了放回来,所以才发生stored proc修好后,ibatis代码还是不工作,直到换了jdbc才修好。
虽然我解释了原因,同事坚持认为ibatis过于复杂。如果它花了他这么长时间来debug,别人也有可能因为种种原因花很多时间来debug别的问题。
2。ibatis只支持一个参数。这个我也解释了,你可以用java bean或者Map。可是同事认为这也是ibatis的学习曲线问题。如果采用,就要求大家都去学ibatis doc才行。
3。同事原来期待的是象Active Record那样的革命性的提高和简化。象ibatis这样还是要进行手工mapping的,对他来说就是没什么太大意义。他不觉得在java里面做这个mapping有什么不好。(我想,也许Hibernate对他更有吸引力。不过把这个系统转换为Hibernate这工作量可大多了)
4。当我说ibatis可以节省很多资源管理的重复代码时,同事说他可以用PersisterCommand。我说PersisterCommand的这些局限性的时候,他说,他不在乎。大不了直接写jdbc。
5。一致性问题。如果同时用jdbc和ibatis,大家就要学两个东西,造成混淆。而如果要把所有东西都换成ibatis,工作量是一个方面,所有的人都要学习ibatis这个代价也是很大的。
6。同事认为cache的那点重复代码无所谓。即使有一些降低cache hit ratio的bug也不是什么大不了的。

最后无法达成统一意见。因为你说什么优点的时候,他只要一句“我不在乎”你就无话可说了。


在这些论点里面,我也认可ibatis的学习曲线和一致性问题。可是,总不能就永远任由这个持久层代码这么滥下去吧?在java这个领域里,我几乎完全相信不可能出现Active Record等价的东西的。而无论Hibernate还是jpa,只怕都是有不下于ibatis的学习曲线和更高的从遗留系统移植的代价吧?

越来越感觉自己不是一个合格的architect。因为我缺乏说服人的能力。

你怎么看这个问题呢?
   发表时间:2007-02-06  
如果简单包装过的JDBC可以解决问题的话,我会选JDBC。理由只有一个:系统设计越简单越好。我做了十几年开发了,见过的各种项目也不少了,真正好的项目都是设计“简单”的项目。

向ajoo和你的同事推销我的DataSet管道技术,见我的《元数据、开放数据模型及动态系统--形而下学篇》,应该比Active Record还要简单。基于JDBC,没有配置,没有子类,完全没有重复代码。
0 请登录后投票
   发表时间:2007-02-06  
楼主你就带头,建好框架,搭建一些iBatis的例子,然后你陪同同事一起使用iBatis,让他们充分感受到iBatis的好。有时间给他们培训一下,学习曲线也怎么难的吧。“1。他和另外一个同事为调试一个使用了ibatis的程序bug花了一天时间。”这个在他们不甚了解iBatis的情况下,肯定会严重排斥iBatis了。当时,如果你在的话,一下子就调试好了,那么他们感觉就会好很多。
0 请登录后投票
   发表时间:2007-02-06  
JDBC封装下,挺好使的
主要的就三把函数 save,update,query
大家都调这么几把函数,协调统一,相当稳健可控
如果不想sql硬编码,那就把他放配置文件
鄙人整的一个综合查询工具,献丑了
http://www.iteye.com/topic/40143

ibatis擅长做复杂查询,insert 与 update 还是hibernate的方式比较帅
可做一个类似hibernate的简易工具,不考虑关联关系(这个是最复杂最难处理的)
ORM变成TOM(table object map)
用TOM来对付insert与update操作,象dynamic save与update都可以轻易的使出来
0 请登录后投票
   发表时间:2007-02-06  
这个不是技术框架的问题,还是人的问题。感觉你的同事很固执。你可以精心准备一次课程,介绍iBTIS引入的效果,做一些对比强烈的例子,预先把他想到的反对理由都准备好相应的反驳策略。
0 请登录后投票
   发表时间:2007-02-06  
iBatis ,感觉维护管理卡发都快
学iBatis也就几个小时就基本会用吧 也就比JDBC学习曲线高一点

iBatis 生成map文件花时间多 不过可以自己写个工具自动生成包括formbean

传参数的化 我觉得用formbean比MAP好点
java 代码
 
  1. public interface BaseDao {  
  2.     public int insert(Object form);  
  3.   
  4.     public int update(Object form);  
  5.   
  6.     public int delete(Object form);  
  7.   
  8.     public Object getInfo(Object form);  
  9.   
  10.     public List getList(Object form);  
  11.   
  12.     public int getCount(Object form);  
  13.   
  14.     public void setNamespace(String namespace);  
  15.   
  16.     public void setInsertkey(String insertkey);  
  17.   
  18.     public void setUpdatekey(String updatekey);  
  19.   
  20.     public void setDeletekey(String deletekey);  
  21.   
  22.     public void setListkey(String listkey);  
  23.   
  24.     public void setCountkey(String countkey);  
  25.   
  26.     public void setInfokey(String infokey);  
  27. }  
java 代码
 
  1. public class GeneralDao  extends SqlMapClientDaoSupport implements BaseDao {  
  2.     private String namespace;  
  3.     private String insertkey = "insert";  
  4.     private String updatekey = "update";  
  5.     private String deletekey = "delete";  
  6.     private String infokey = "getInfo";  
  7.     private String listkey = "getList";  
  8.     private String countkey = "getCount";  
  9.     public int insert(Object form) {  
  10.         try {  
  11.             getSqlMapClientTemplate().insert(namespace+"."+insertkey,form);  
  12.             return 1;  
  13.         } catch (Exception e) {  
  14.             e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.  
  15.             return 0;  
  16.         }  
  17.           //To change body of implemented methods use File | Settings | File Templates.  
  18.     }  
  19.   
  20.     public int update(Object form) {  
  21.         return getSqlMapClientTemplate().update(namespace+"."+updatekey,form);  //To change body of implemented methods use File | Settings | File Templates.  
  22.     }  
  23.   
  24.     public int delete(Object form) {  
  25.         return getSqlMapClientTemplate().delete(namespace+"."+deletekey,form);  //To change body of implemented methods use File | Settings | File Templates.  
  26.     }  
  27.   
  28.     public Object getInfo(Object form) {  
  29.         return getSqlMapClientTemplate().queryForObject(namespace+"."+infokey,form);  //To change body of implemented methods use File | Settings | File Templates.  
  30.     }  
  31.   
  32.     public List getList(Object form) {  
  33.         return getSqlMapClientTemplate().queryForList(namespace+"."+listkey,form);  //To change body of implemented methods use File | Settings | File Templates.  
  34.     }  
  35.   
  36.     public int getCount(Object form) {  
  37.         return Integer.parseInt(String.valueOf(getSqlMapClientTemplate().queryForObject(namespace+"."+countkey, form)));  
  38.     }  
  39.   
  40.     public void setNamespace(String namespace) {  
  41.         this.namespace = namespace;  
  42.     }  
  43.   
  44.     public void setInsertkey(String insertkey) {  
  45.         this.insertkey = insertkey;  
  46.     }  
  47.   
  48.     public void setUpdatekey(String updatekey) {  
  49.         this.updatekey = updatekey;  
  50.     }  
  51.   
  52.     public void setDeletekey(String deletekey) {  
  53.         this.deletekey = deletekey;  
  54.     }  
  55.   
  56.     public void setListkey(String listkey) {  
  57.         this.listkey = listkey;  
  58.     }  
  59.   
  60.     public void setCountkey(String countkey) {  
  61.         this.countkey = countkey;  
  62.     }  
  63.   
  64.     public void setInfokey(String infokey) {  
  65.         this.infokey = infokey;  
  66.     }  
  67. }  
目前这样实现的单DAO

0 请登录后投票
   发表时间:2007-02-06  
使用新工具(新方法)的代价是如此之小,带来的好处却是如此之多!” —— 只有当你你做到令大多数人相信这一点,才能很好的推进自己的“新计划”。

对于legacy builds,推不动看起来“全新”的方法也是正常的,因为它们看起来太不安全可靠了,况且谁都不愿意承认以往的作品存在着“可以大量改进的地方”。

只要是健康运作的公司,就必然会有公司内部的“政治问题”,搞技术的时候也不妨考虑一下自己的建议和观念会对现有的“政治格局”产生怎样的影响……

另外,我必须得说,这种不喜欢用新东西来帮自己擦屁股的态度才是相对而言比较良性的。对比于国内的很多技术人员老喜欢找新东西来替自己过去产品的缺陷遮羞来说,这种态度还是挺有益处的呢。

对于那些对自身撰写JDBC代码能力很有自信的团队而言,通常容易做到“代价小,好处多”的是:
JDBC ---->> commons-DbUtils 而不是 JDBC ---->> iBatis

个人从来没有真正做过构架师(architect),以上言论仅是个人现有见解。
0 请登录后投票
   发表时间:2007-02-06  
个人觉得用JDBC不如用Spring的JdbcTemplate
用Spring的JdbcTemplate不如用Spring的SqlMapClientTemplate + iBATIS
不必自己封装,光iBATIS2一个版本已经为java社区服务了三年,还有.net,ruby的版本。

可以自己把框架搭起来,演示一下功能、性能和开发效率。
如果元老连这么一点框架学习的曲线都接受不了或“我不在乎”,那也真老了。
0 请登录后投票
   发表时间:2007-02-06  
搞技术的人就是这样-固执,所以做技术人员真是难上加难,整天对着的也是这样的人,“将心比心”也许是解决问题的好办法,很多时候技术问题背后藏着别的什么问题呢
0 请登录后投票
   发表时间:2007-02-06  
我在这里列出里为什么选择ibatis的理由
http://ivanl.iteye.com/blog/24738
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics