最近在做一个小的功能时需要使用JDBC,目的是写几个API给别的开发人员使用,方便他们忽略数据库连接、关闭、资源释放等比较繁琐的工作,简单使用我写的API进行CRUD操作。目前现有的一些框架如Hibernate、iBatis等当然可以完成这些功能,但由于我做的这个项目实在太小,专门去配置环境用框架就显得有点儿臃肿、费事了。总而言之,我需要自己实现,其中最麻烦的部分就是资源释放了。
使用过JDBC的同学都知道资源用完之后要马上释放,否则会资源泄漏。数据库连接的资源我们可以在最后进行释放,比较麻烦的是statement和ResultSet的释放,一开始我的设想是写一个类似如下的函数:
// 错误方案一public ResultSet query(String sql) { connection = getConnection(); statement = connection.prepareStatement(sql); ResultSet rs = statement.executeQuery(sql); return rs;}
但是这样做有很大问题,比如statement和ResultSet都没有释放,ResultSet还可以指望好心的用户调用一下close()方法释放,但是statement呢?于是后来又想到下面这个方法:
// 错误方案二public void query(String sql) { connection = getConnection(); statement = connection.prepareStatement(sql); ResultSet rs = statement.executeQuery(sql); while(rs.next()) { // 执行具体的操作,例如把结果写到某个文件中 } rs.close(); statement.close();}
这样做虽然把资源释放了,但是却让代码的耦合度更高了。这个query函数是把结果写入文件,那么假如用户还需要把结果保存到某个对象中该怎么办呢?那就只好再写一个类似的函数,大部分代码都是重复的,只是while部分的代码会变。虽然也可以把这部分代码抽取出来作为另外一个函数,但是一旦有新的对ResultSet的操作加入进来,这个类文件势必要更改,明显不满足开闭原则。
解决办法
后来突然想到Spring中有个JdbcTemplate,在web开发中可以更Hibernate等框架结合使用,方法进行细粒度的数据库操作。使用JdbcTemplate的时候我们并没有关心事务、资源等东西,直接就是用SQL语句了,查了一下JdbcTemplate的使用,真有一种恍然大悟的感觉,它使用的就是我们很熟悉的回调函数机制!一个简单的操作例子如下:jdbcTemplate.query(sql, new RowCallbackHandler() { // 匿名内部类 public void processRow(ResultSet rs) throws SQLException { // 根据个人需要去具体操作rs }}
看到这个JdbcTemplate的使用例子之后,我也想到了我该怎么写我的query函数,如下:
// 正确方案// CallbackInterface 是自己定义的一个接口,用户可以实现这个接口,然后调用query函数public void query(String sql, CallbackInterface processor) { connection = getConnection(); statement = connection.prepareStatement(sql); ResultSet rs = statement.executeQuery(sql); processor.processResultSet(rs); rs.close(); statement.close();}
一点感想
其实这个方法并不陌生,甚至可以说非常常用,例如在Android开发的时候我们经常用到Handler,在一个线程中联网获取数据,成功之后要更新界面就是通过回调函数。因为消息捕获是由框架去做的,捕获到消息之后必须马上处理,但又不知道用户具体想怎么处理,这时候用回调机制是再好不过了。在我这个项目中,获取到数据库的查询结果之后也是需要马上处理(因为要释放资源嘛),但又不知道具体要怎么处理,这时候也是用回调机制可以很好地解决问题。总结一下就是,如果我写一个API去获取某些数据,获取到之后要把数据给用户去做具体操作,同时用户操作完之后控制权还要回到我的手中,那么回调机制是很好的解决方案。另外,AOP也是同样的思想,用户自定义在切入点前后需要被执行的回调函数,然后框架会帮我们去调这些函数,从而实现如事务管理等功能。