JDBC编程经验谈

来源:互联网 发布:nginx访问静态图片 编辑:程序博客网 时间:2024/06/18 08:19
JDBC编程经验谈

Java为数据库访问制定了一套统一的访问接口——JDBC,通过使用JDBC你可以不用去关心具体操作的什么数据库,不管是Oracle、DB2或者是SQL Server等等。下面就是JDBC程序的层次结构:

JDBC应用程序 <-> JDBC <-> JDBC Drivers <-> Database

也就是说从应用程序的能见度上来看,除非你有特殊要求,否则应用程序只应该和JDBC接口打交道,也就是我们常用的Connection,Statement以及ResultSet三个接口以及一些数据类型对象。可为什么很多人在使用某一个数据库系统开发出来的程序移植到其他数据库的时候却经常出现这样那样的问题呢?

问题在于:开发人员使用的虽然是JDBC中定义的数据库访问接口,但却没有使用JDBC规范中定义的数据类型,为什么这样说呢?我们来看看下面一段小程序

public void jdbcTest(Connection conn, String name, String time)throws SQLException{
  String sql = "SELECT * FROM table1 WHERE f_name='"+name+"' AND reg_time>'"+time+"'";
  Statement ps = conn.createStatement();
  ResultSet rs = ps.executeQuery(sql);
  while(rs.next()){
   //..读取数据..
  }
  rs.close();
  ps.close();
}

不知道大家有没有发现这段程序存在什么样的问题?这段程序至少存在两个问题也就是本文想说的两种“反模式”,因而提出下面两个经验谈:

1. 数据库无关的数据类型

在这段程序中,没错,都是在跟JDBC接口打交道,可是这样的程序如果在Oracle数据库下运行会失败,原因很多人很都能立即就说了出来,因为时间的格式不对等等,但是如果让他来改,估计会把时间的格式调整一下。可当要求这段程序必须同时允许支持Oracle之外的数据库时,往往会束手无策。

还有另外一个问题就是name参数的问题,如果参数name中包含着SQL语言中的保留关键字或者特殊符号的时候,此代码也是无法工作。反对者表示可以在传入前对参数进行符号替换处理,可我觉得这样做第一是无法面面俱到而且也是多此一举。

在JDBC编程中,唯一不是数据库无关的内容就只有SQL语句了,因为往往数据库对某些数据类型有着特殊的要求。但是数据库也要遵循一些标准,例如SQL92等,因此只要我们按照SQL92标准来编写SQL语句的时候,那么我们的程序肯定可以兼容各种数据库系统。可是SQL标准中并没有指定应该以什么样的格式来表示时间类型数据,因此时间参数似乎成为我们的绊脚石。

事实上,JDBC接口已经为我们考虑到了该问题,java.sql包中有三个时间相关的类型java.sql.Date, java.sql.Time, java.sql.Timestamp。相信很多人没有用过这三个类,它们分别表示日期类型、时间类型、日期时间类型。只要我们用这三个类型来表示时间参数或者查询结果,也就不存在不同数据库使用不同格式来表示时间的问题了。

那么到底发生了什么事情使我们不需要再关心时间字段的格式呢?其实很简单,JDBC只是定义了数据库访问的接口,真正的数据库访问代码是封装在数据库厂商所提供的JDBC驱动程序中,由它来负责将java.sql中的时间类型转换成数据库特有的格式。另外包括字符串存在的特殊符号或者关键字也由该驱动程序负责处理,这就是我之前说的我们没有必要多此一举的原因所在。

本文的最后会给出修正后的jdbcTest代码来解释如何使用JDBC规范中的时间对象。

2. 资源的释放问题

jdbcTest方法还存在另外一个问题就是关于资源没有正确被释放的问题,编写该代码的程序员使用了一种假设,他假设所有的代码都能成功执行并不会有异常发生,于是代码也就成了分配语句资源、执行语句、读取结果、释放资源。可以一旦在执行语句或者读取运行结果时发生异常时会导致什么情况呢?释放资源部分的代码会因此没有被执行,于是所分配的资源成了无法再次使用而且被挂起的情况,这也是很多开发者反映的系统运行一段时间后无法获取数据库连接或者说是其他一些莫名奇怪的原因导致程序无法正常工作。以前使用Informix数据库的时候就是一个无可用游标的错误产生。

要保证资源被可靠的释放,必须结合Java语言的异常处理机制,接下来我们对jdbcTest方法进行改造如下,大家可对比两段程序,找出问题的真正所在。

public void jdbcTest1(Connection conn, String name, Date time) throws SQLException{
  //使用?来替换原有的参数,使SQL符合标准
  String sql = "SELECT * FROM t_user WHERE f_name=? AND reg_time>?";
  PreparedStatement ps = null;//使用PreparedStatement替代Statement
  ResultSet rs = null;
  try{
   ps = conn.prepareStatement(sql);
   ps.setString(1, name); //无需进行字符串参数的任何处理
   ps.setTimestamp(2, new Timestamp(time.getTime()));
   rs = ps.executeQuery();
   while(rs.next()){
    //..读取数据..
    rs.getDate("birth_date");
    rs.getTimestamp("reg_time");
   }
  }finally{
   //保证异常发生后下面代码被有效的执行
   if(rs!=null) rs.close();
   if(ps!=null) ps.close();
  }
}

当然也不是所有的参数都要求使用动态绑定的方式来处理,例如整数类型就可以直接编写在SQL语句中来减小代码量。但是对于类似字符串、时间或者其他大字段一定要养成习惯,坚决使用动态绑定的方式来设置参数值,同时资源的释放不要图省事。

把这两点牢记在心,并贯彻到实际的代码编写工作上,你会发现JDBC编程方面的困扰少了很多。至于编码的问题可以看我发表在IBM上的一篇文章《一劳永逸的数据库编码解决方案》,地址是:
http://www-900.ibm.com/developerWorks/cn/java/l-java-data/

原创粉丝点击