Inside JDBC(三)
在上一篇InsideJDBC(二)中,介绍了Connection接口的功能,以及DatabaseMetaData接口。本篇主要介绍
SQL语句对象及结果集。
数据库连接建立好之后,我们就可以与数据库进行交互了,这种交互主要体现为执行各种SQL语句。SQL
语句的执行需要Statement对象来完成。为了讲述SQL语句对象的具体用法,先给大家介绍结果集(ResultSet)
结果集由java.sql.ResultSet接口定义,接口起到定义规范(标准)的作用,就是说任何JDBC驱动都需要
实现ResultSet接口,提供封装查询结果的功能,从而符合JDBC规范。在Connector/J中,实现ResultSet接口的
类是com.mysql.jdbc.ResultSetImpl类。
我们可以简单把结果集理解成执行Select语句之后符合条件的数据从数据库传输到内存中形成的一张内存
数据表,此表具有表头、列索引、行索引以及定位用的行指示器。行、列索引都是从1开始计数行指示器停留
在当前行,但是对于刚刚产生的结果集,行指示器停留在第一行之前。那么如何来移动行指示器呢?大家肯定
能猜到了ResultSet定义了用于移动行指示器的方法,但是别急,移动行指示器是有前提的,就是要看结果集的
类型。结果集类型的指定用以下代码:
Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = stmt.executeQuery("SELECT a, b FROM TABLE2");
这里只关心ResultSet内部定义的几个静态常量,其他的稍后会说明,
ResultSet.TYPE_SCROLL_INSENSITIVE
说明结果集的行指示器可向前、向后移动,甚至绝对定位;同时此结果集忽略其他数据库事务对数据库中在当前
结果集中已选定数据的更新。
ResultSet.TYPE_FORWARD_ONLY
说明结果集的行指示器只可向前移动,直到结束。
ResultSet.TYPE_SCROLL_SENSITIVE
说明结果集的行指示器可向前、向后移动,甚至绝对定位;同时此结果集显示其他数据库事务对数据库中在当前
结果集中已选定数据的更新。
ResultSet.CONCUR_UPDATABLE
说明结果集是可更新的,并使对应数据库中数据更新。
ResultSet.CONCUR_READ_ONLY
说明结果集是不可可更新的,不能使用对结果集数据的更新来更新数据库中数据。
ResultSet.CLOSE_CURSORS_AT_COMMIT
说明使用Connection.commit()提交事务时,结果集关闭。
ResultSet.HOLD_CURSORS_OVER_COMMIT
说明使用Connection.commit()提交事务时,结果集保留。
接下来看一个具体的实例,已知MySQL的test数据库中有一表(station)数据如下:
+-----------+----------+---------+---------+---------+-----------+
|stationId|trainNum|station|outTime|dayTime|sitePrice|
+-----------+----------+---------+---------+---------+-----------+
|1|k339|北京|12:37|1|0.00|
|2|k339|秦皇岛|17:06|1|44.00|
|3|k339|沈阳|22:30|1|99.00|
|4|k339|哈尔滨|04:37|2|154.00|
+-----------+----------+---------+---------+---------+-----------+
字段定义如下:(此处只用于演示,不考虑表设计是否优良)
+-----------+--------------+------+-----+---------+----------------+
|Field|Type|Null|Key|Default|Extra|
+-----------+--------------+------+-----+---------+----------------+
|stationId|int(11)|NO|PRI|NULL|auto_increment|
|trainNum|varchar(20)|NO||NULL||
|station|varchar(20)|NO||NULL||
|outTime|varchar(5)|NO||NULL||
|dayTime|int(11)|NO||NULL||
|sitePrice|decimal(6,2)|YES||NULL||
+-----------+--------------+------+-----+---------+----------------+
代码如下:package com.wwei.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class ResultSetType { public static void main(String[] args) { Connection con = getConnection(); String sql = "select stationId,trainNum,station,outTime,dayTime,sitePrice from station where trainNum='k339'"; if(con != null){ handleData(getData(sql, con)); releaseConnection(con); } } private static Connection getConnection() { try { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "wwei"); } catch (ClassNotFoundException e) { System.err.println("类路径中没有MySQL驱动jar库文件"); } catch (SQLException e) { System.err.println("建立数据库连接期间发生异常:" + e.getMessage()); } return null; } private static ResultSet getData(String sql, Connection con) { Statement stmt = null; ResultSet rs = null; try { stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); rs = stmt.executeQuery(sql); return rs; } catch (SQLException e) { System.err.println("获得数据期间发生异常:" + e.getMessage()); } return rs; } private static void handleData(ResultSet rs) { System.out.println("K339次列车一览表:"); System.out.println("行号\t\t\t编号\t\t\t站名\t\t\t发车时间\t\t\t 票价"); if (rs != null) { try { while (rs.next()) { System.out.print(rs.getRow() + "\t\t\t"); System.out.print(rs.getInt(1) + "\t\t\t"); System.out.print(rs.getString(3) + "\t\t\t"); System.out.print(rs.getString("outTime") + "\t\t\t"); System.out.print(rs.getDouble(6) + "\n"); } } catch (SQLException e) { System.err.println("处理结果集期间发生异常:" + e.getMessage()); } } else { System.err.println("获取数据出错,请查看错误消息。"); } } private static void releaseConnection(Connection con) { try { if (con != null && !con.isClosed()) { con.close(); } } catch (SQLException e) { System.err.println("关闭数据库连接期间发生异常:" + e.getMessage()); } } }
在getData方法中,指定ResultSet的类型为只读不可滚动的结果集(CONCUR_READ_ONLY,TYPE_FORWARD_ONLY)。
ResultSet还提供了多个导航方法,分别如下:booleanabsolute(introw)绝对定位到row指定的行,row如果是负数则相对最后一行。例如定位到第五行为
absolute(5),定位到最后一行absolute(-1);
voidafterLast()定位到最后一行之后。
voidbeforeFirst()定位到第一行之前。
booleanfirst()定位到第一行。
booleanisAfterLast()是否位于最后一行之后。
booleanisBeforeFirst()是否位于第一行之前。
booleanisFirst()是否位于第一行。
booleanisLast()是否位于最后一行。
booleanlast()定位到最后一行。
booleannext()逐行前移。
booleanrelative(introws)相对当前位置移动rows行,rows可正可负,0时不移动。
booleanprevious()逐行后退。
如果需要把上例数据倒序输出,需要把handleData改为:
private static void handleData(ResultSet rs) { System.out.println("K339次列车一览表:"); System.out.println("行号\t\t\t编号\t\t\t站名\t\t\t发车时间\t\t\t 票价"); if (rs != null) { try { rs.afterLast();//修改处 while (rs.previous()) {//修改处 System.out.print(rs.getRow() + "\t\t\t"); System.out.print(rs.getInt(1) + "\t\t\t"); System.out.print(rs.getString(3) + "\t\t\t"); System.out.print(rs.getString("outTime") + "\t\t\t"); System.out.print(rs.getDouble(6) + "\n"); } } catch (SQLException e) { System.err.println("处理结果集期间发生异常:" + e.getMessage()); } } else { System.err.println("获取数据出错,请查看错误消息。"); } }
首先需要把行指示器定位于结果集最后一行之后,使用rs.afterLast(),再逐行后退rs.previous();
同时把getData改为:private static ResultSet getData(String sql, Connection con) { Statement stmt = null; ResultSet rs = null; try { stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,//修改处,或者是ResultSet.TYPE_SCROLL_SENSITIVE ResultSet.CONCUR_READ_ONLY);//或者是ResultSet.CONCUR_UPDATABLE rs = stmt.executeQuery(sql); return rs; } catch (SQLException e) { System.err.println("获得数据期间发生异常:" + e.getMessage()); } return rs; }
如果getData方法仍然是ResultSet.TYPE_FORWARD_ONLY,是不允许rs.afterLast()这些非next()操作的。(Connector/J
没有严格遵守JDBC规范,允许TYPE_FORWARD_ONLY下非next()操作,要试验需要用其他驱动,如Oracle驱动)。
ResultSet还提供了从当前行获取数据的方法getXXX(),参数可以使列索引或者是结果集“表头”的列名。
如果把上例的sql语句改为:
Stringsql="selectstationId,trainNum,station,outTime,dayTime,sitePriceas价格fromstationwheretrainNum='k339'";
第三列的数据可以用rs.getDouble(3)或者rs.getDouble("价格")获得。
ResultSet还提供了修改数据的方法:
voidupdateXXX(intcolumnIndex,XXXvalue)或者voidupdateXXX(intcolumnName,XXXvalue)用于标示当前行
columnIndex或columnName指定列用新数据value进行更新。
voidupdateRow()用于发出更新操作。
需要注意的是,如果需要更新数据,需要结果集中包含主键数据,上述例题中为station表的stationId列;同时
结果集类型需要时ResultSet.CONCUR_UPDATABLE类型。例如在本例中我们新增更新方法:
private static void updateData(int rowIndex,int columnIndex,Object value,ResultSet rs){ try { rs.absolute(rowIndex); rs.updateObject(columnIndex, value); rs.updateRow(); } catch (SQLException e) { System.err.println("更新数据时出错:" + e.getMessage()); } }
main方法修改为:
public static void main(String[] args) { Connection con = getConnection(); String sql = "select stationId,trainNum,station,outTime,dayTime,sitePrice from station where trainNum='k339'"; if (con != null) { updateData(3 ,6,new Double(101.0), getData(sql, con)); handleData(getData(sql, con)); releaseConnection(con); } }
插入新行涉及的方法有:
voidmoveToInsertRow()、voidmoveToCurrentRow()、voidinsertRow()、
新增一方法:private static void newRow(ResultSet rs){ try { rs.moveToInsertRow(); rs.updateString(2,"D51"); rs.updateString(3,"北京"); rs.updateString(4,"09:05"); rs.updateString(5,"1"); rs.updateDouble(6, 0.0); rs.insertRow(); rs.moveToCurrentRow(); } catch (SQLException e) { System.err.println("新增行时出错:" + e.getMessage()); } }
moveToInsertRow()把行指示器移动到用于新增行的临时区域,用updateXXX方法标记新数据,再用insertRow方法发出insert操作,最后用moveToCurrentRow方法把行指示器移回当前行。
如果需要删除当前行数据,只需调用rs.deleteRow()方法,这项功能相对简单,就不举例了,节省篇幅。SQL语句对象还是留到下篇在描述吧。