jdbc还是ibatis?
公司的一个大系统的持久层一直是直接使用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。因为我缺乏说服人的能力。
你怎么看这个问题呢?package edu.jlu.fuliang.library.jdbc;import java.util.ArrayList;import java.util.List;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException; public class JdbcTemplate { public List query(String sql,RowCallBackHandler handler){ Connection connection = JdbcUtils.createConnection(); PreparedStatement ps = JdbcUtils.createPreparedStatement(sql,connection); ResultSet rs = null; List list = new ArrayList(); try { rs = ps.executeQuery(); while(rs.next()){ list.add(handler.processRow(rs)); } } catch (SQLException e) { e.printStackTrace(); }finally{ JdbcUtils.closeStatement(ps); JdbcUtils.relaseConnection(connection); } return list; } public Object queryForObject(String sql,RowCallBackHandler handler){ Connection connection = JdbcUtils.createConnection(); PreparedStatement ps = JdbcUtils.createPreparedStatement(sql,connection); ResultSet rs = null; Object o = null; try { rs = ps.executeQuery(); rs.next(); o = handler.processRow(rs); } catch (SQLException e) { e.printStackTrace(); }finally{ JdbcUtils.closeStatement(ps); JdbcUtils.relaseConnection(connection); } return o; } public void executeUpdate(String sql,PreparedStatementCallBack pareparedStatementCallBack){ Connection connection = JdbcUtils.createConnection(); PreparedStatement ps = JdbcUtils.createPreparedStatement(sql,connection); try { pareparedStatementCallBack.doInStatement(ps); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); }finally{ JdbcUtils.closeStatement(ps); JdbcUtils.relaseConnection(connection); } }}//回调接口RowCallBackHandlerpackage edu.jlu.fuliang.library.jdbc; import java.sql.ResultSet; public interface RowCallBackHandler { public Object processRow(ResultSet rs);}///回调接口PreparedStatementCallBackpackage edu.jlu.fuliang.library.jdbc; import java.sql.PreparedStatement;import java.sql.SQLException; public interface PreparedStatementCallBack { public void doInStatement(PreparedStatement stmt)throws SQLException;}//ORMaping接口package edu.jlu.fuliang.library.jdbc; import java.sql.ResultSet; public interface ORMaping { public Object mapping(ResultSet rs,Object o);}//JdbcUtils.javapackage edu.jlu.fuliang.library.jdbc; import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.SQLException;import java.sql.Statement; public class JdbcUtils { public static Connection createConnection(){//为了简单,进行了硬编码 String user = "root"; String passwd = "123456"; String url = "jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=gb2312"; Connection connection = null; try{ Class.forName("com.mysql.jdbc.Driver").newInstance(); connection = DriverManager.getConnection(url,user,passwd); } catch(Exception e){ e.printStackTrace(); } return connection; } public static PreparedStatement createPreparedStatement(String sql,Connection connection){ PreparedStatement stmt = null; try{ stmt = connection.prepareStatement(sql); }catch(Exception e){ e.printStackTrace(); } return stmt; } public static void closeStatement(Statement s){ try { s.close(); } catch (SQLException e) { e.printStackTrace(); } } public static void relaseConnection(Connection connection){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } }}//以下是数据访问对象://BorrowerDao.java package edu.jlu.fuliang.library.dao; import edu.jlu.fuliang.library.domain.Borrower; public interface BorrowerDao { public Borrower queryByLastName(String lastName); public void updateBorrower(Borrower borrower); public void insertBorrower(Borrower borrower); public void deleteBorrowerByLastName(String lastName);}// BookDao.javapackage edu.jlu.fuliang.library.dao; import java.util.List;import edu.jlu.fuliang.library.domain.Book; public interface BookDao { public Book queryByTitle(String title); public List queryByAuthor(String author); public List queryByBorrowerLastName(String firstName); public void updateBook(Book book); public void insertBook(Book book); public void deleteBookByTile(String title);}// BorrowOrReturnDao.javapackage edu.jlu.fuliang.library.dao; public interface BorrowOrReturnDao { public void borrowBook(String borrowerLastName,String bookTile); public void returnBook(String borrowerLastName,String bookTile);}//抽象工厂:package edu.jlu.fuliang.library.dao; public interface DaoFactory { public BookDao createBookDao(); public BorrowerDao createBorrowerDao(); public BorrowOrReturnDao createBorrowOrReturnDao();}//具体工厂:package edu.jlu.fuliang.library.dao; import edu.jlu.fuliang.library.dao.mysql.BookDaoImpl;import edu.jlu.fuliang.library.dao.mysql.BorrowerDaoImpl; public class MysqlDaoFactory implements DaoFactory{ public BookDao createBookDao() { return new BookDaoImpl(); } public BorrowerDao createBorrowerDao() { return new BorrowerDaoImpl(); } public BorrowOrReturnDao createBorrowOrReturnDao() { return new BorrowOrReturnDaoImpl(); }}//BookDao的具体实现://BookDaoImpl.javapackage edu.jlu.fuliang.library.dao.mysql; import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List; import edu.jlu.fuliang.library.dao.BookDao;import edu.jlu.fuliang.library.domain.Book;import edu.jlu.fuliang.library.jdbc.*; public class BookDaoImpl implements BookDao{ private JdbcTemplate jdbcTemplate = new JdbcTemplate(); public Book queryByTitle(String title) { String sql = "select * from book where title = "+title; return (Book)jdbcTemplate.queryForObject(sql,new RowCallBackHandler(){ public Object processRow(ResultSet rs) { BookMaping map = new BookMaping(); Book book = (Book)map.mapping(rs,new Book()); return book; } }); } public List queryByAuthor(String author) { String sql = "select * from book where author = "+author; return jdbcTemplate.query(sql,new RowCallBackHandler(){ public Object processRow(ResultSet rs) { BookMaping map = new BookMaping(); Book book = (Book)map.mapping(rs,new Book()); return book; } }); }//Borrower的具体实现://BookDaoImpl.javapackage edu.jlu.fuliang.library.dao.mysql; import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException; import edu.jlu.fuliang.library.dao.BorrowerDao;import edu.jlu.fuliang.library.domain.Borrower;import edu.jlu.fuliang.library.jdbc.JdbcTemplate;import edu.jlu.fuliang.library.jdbc.PreparedStatementCallBack;import edu.jlu.fuliang.library.jdbc.RowCallBackHandler; public class BorrowerDaoImpl implements BorrowerDao{ private JdbcTemplate jdbcTemplate = new JdbcTemplate(); public Borrower queryByLastName(String lastName) { String sql = "select * from borrower where lastName = " + lastName; return (Borrower)jdbcTemplate.queryForObject(sql,new RowCallBackHandler(){ public Object processRow(ResultSet rs) { BorrowerMaping borrowerMaping = new BorrowerMaping(); return borrowerMaping.mapping(rs,new Borrower()); } }); } public void updateBorrower(final Borrower borrower) { String sql = "update borrower set lastname = ?,firstname=?" + ",address =?,city =?,zip=?,state=? where lastname = ?"; jdbcTemplate.executeUpdate(sql,new PreparedStatementCallBack(){ public void doInStatement(PreparedStatement stmt) throws SQLException { stmt.setString(1,borrower.getLastName()); stmt.setString(2,borrower.getFirstName()); stmt.setString(3,borrower.getAddress()); stmt.setString(4,borrower.getCity()); stmt.setString(5,borrower.getZip()); stmt.setString(6,borrower.getState()); stmt.setString(7,borrower.getLastName()); } }); } public void insertBorrower(final Borrower borrower) { String sql = "insert into borrower values(?,?,?,?,?,?)"; jdbcTemplate.executeUpdate(sql,new PreparedStatementCallBack(){ public void doInStatement(PreparedStatement stmt) throws SQLException { stmt.setString(1,borrower.getLastName()); stmt.setString(2,borrower.getFirstName()); stmt.setString(3,borrower.getAddress()); stmt.setString(4,borrower.getCity()); stmt.setString(5,borrower.getZip()); stmt.setString(6,borrower.getState()); } }); } public void deleteBorrowerByLastName(final String lastName) { String sql = "delete from borrower where lastName = ?"; jdbcTemplate.executeUpdate(sql,new PreparedStatementCallBack(){ public void doInStatement(PreparedStatement stmt) throws SQLException { stmt.setString(1,lastName); } }); } }// BorrowOrReturnDaoImpl.javapackage edu.jlu.fuliang.library.dao.mysql; import java.sql.PreparedStatement;import java.sql.SQLException; import edu.jlu.fuliang.library.dao.BorrowOrReturnDao;import edu.jlu.fuliang.library.jdbc.JdbcTemplate;import edu.jlu.fuliang.library.jdbc.PreparedStatementCallBack; public class BorrowOrReturnDaoImpl implements BorrowOrReturnDao{ private JdbcTemplate jdbcTemplate = new JdbcTemplate(); public void borrowBook(final String borrowerLastName, final String bookTitle) { String sql = "insert into book_borrower values(?,?)"; jdbcTemplate.executeUpdate(sql,new PreparedStatementCallBack(){ public void doInStatement(PreparedStatement stmt) { try{ stmt.setString(1,borrowerLastName); stmt.setString(2,bookTitle); }catch(SQLException e){ e.printStackTrace(); } } }); } public void returnBook(final String borrowerLastName, final String bookTitle) { String sql = "delete from book_borrower where lastname = ?,title = ?"; jdbcTemplate.executeUpdate(sql,new PreparedStatementCallBack(){ public void doInStatement(PreparedStatement stmt) { try{ stmt.setString(1,borrowerLastName); stmt.setString(2,bookTitle); }catch(SQLException e){ e.printStackTrace(); } } }); }}//book的ORMaping的实现:// BookMaping.javapackage edu.jlu.fuliang.library.dao.mysql; import java.sql.ResultSet;import java.sql.SQLException; import edu.jlu.fuliang.library.domain.Book;import edu.jlu.fuliang.library.jdbc.ORMaping; public class BookMaping implements ORMaping{ public Object mapping(ResultSet rs, Object o) { Book book = (Book)o; try { book.setTitleName(rs.getString("titleName")); book.setAuthor(rs.getString("author")); book.setISBN(rs.getString("isbn")); book.setType(rs.getInt("type")); book.setItemsAvailable(rs.getInt("itemAvailable")); } catch (SQLException e) { e.printStackTrace(); } return book; } }////borrower的ORMaping的实现:// BorrowerMaping.javapackage edu.jlu.fuliang.library.dao.mysql; import java.sql.ResultSet;import java.sql.SQLException; import edu.jlu.fuliang.library.domain.Borrower;import edu.jlu.fuliang.library.jdbc.ORMaping; public class BorrowerMaping implements ORMaping{ public Object mapping(ResultSet rs, Object o) { Borrower borrower = (Borrower)o; try { borrower.setLastName(rs.getString(1)); borrower.setFirstName(rs.getString(2)); borrower.setAddress(rs.getString(3)); borrower.setCity(rs.getString(4)); borrower.setZip(rs.getString(5)); borrower.setState(rs.getString(6)); } catch (SQLException e) { e.printStackTrace(); } return borrower; } } 52 楼 ajoo 2007-02-26 intolong 写道ajoo不是写过jdbc template么?
http://www.iteye.com/topic/7068
和你同事那个差不多吧,一个继承,一个组合
他这个还不如我的那个。他的这个实现没有处理Error的情况。在Error出现的情况,不会回滚事务。
不管我的还是同事的这个都有若干个问题:
1。不支持cache。还要自己写cache,不如ibatis直接提供的cache支持方便。
2。不支持一个connection多个query或者命令。
53 楼 抛出异常的爱 2007-02-26 jdbc 不是架构
ibatis是架构
要用什么就看哪个架构省力
如果要升级那就要看是否非要升级所有的文件
原有的有必要修改的代码也可以用重构的方式完成
由于jdbc可以离开架构而存在
所以没必要非把所有的代码都移到新的架构上面
54 楼 spiritfrog 2007-04-13 这根本就不是技术问题,都是人的问题,人家固执保守,不肯学习新东西,确实就很无奈。当architect没有他人的拥护看来也是不行的。 55 楼 fixopen 2007-08-15 屁股决定脑袋。
因为自己的立场和利益,每个人都会坚持一些从别人看来不可理解的东西,都会时不时的担心一下这个新的外来者是不是会损害自己的利益。
最让你感觉到无能为力的是“我不care”,但这其实也是你最大的攻击点,问问他care什么?告诉他什么能帮助他更好的care这些东西。当然,一定不能忘了工程的投入产出这个最大的砝码。 56 楼 lszone 2007-08-15 ibatis麻烦?你用hibernate试试 57 楼 zagile 2007-08-16 原来的系统跑得好好的,需要很强的理由才能说服当官的决定替换。