在上一篇Inside JDBC(二)中,介绍了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还提供了多个导航方法,分别如下:
boolean absolute(int row)绝对定位到row指定的行,row如果是负数则相对最后一行。例如定位到第五行为
absolute(5),定位到最后一行absolute(-1);
void afterLast() 定位到最后一行之后。
void beforeFirst() 定位到第一行之前。
boolean first() 定位到第一行。
boolean isAfterLast() 是否位于最后一行之后。
boolean isBeforeFirst() 是否位于第一行之前。
boolean isFirst() 是否位于第一行。
boolean isLast() 是否位于最后一行。
boolean last() 定位到最后一行。
boolean next() 逐行前移。
boolean relative(int rows)相对当前位置移动rows行,rows可正可负,0时不移动。
boolean previous() 逐行后退。
如果需要把上例数据倒序输出,需要把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语句改为:
String sql = "select stationId,trainNum,station,outTime,dayTime,sitePrice as 价格 from station where trainNum='k339'";
第三列的数据可以用rs.getDouble(3)或者rs.getDouble("价格")获得。
ResultSet还提供了修改数据的方法:
void updateXXX(int columnIndex,XXX value)或者void updateXXX(int columnName,XXX value)用于标示当前行
columnIndex或columnName指定列用新数据value进行更新。
void updateRow()用于发出更新操作。
需要注意的是,如果需要更新数据,需要结果集中包含主键数据,上述例题中为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);
}
}
插入新行涉及的方法有:
void moveToInsertRow()、void moveToCurrentRow()、 void insertRow()、
新增一方法:
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语句对象还是留到下篇在描述吧。