Inside JDBC(三)

mikean 2009-04-07

在上一篇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语句对象还是留到下篇在描述吧。

相关推荐