JAVA应用程序设计和开发环境-JDBC编程
第一堂 JDBC编程
JDBC(Java Data Base Connectivity)是Java语言为了支持SQL功能而提供的与数据库相联的用户接口,JDBC中包括了一组由Java语言书写的接口和类,它们都是独立于特定的DBMS,或者说它们可以和各种数据相关联。有了JDBC以后,程序员可以方便地在Java语言中使用SQL语言,从而使Java应用程序或Java applet可以实现对分布在网络上的各种关系数据库的访问。使用了JDBC以后,程序员可以将精力集中于上层的功能实现,而不必关心底层与具体的DBMS的连接和访问过程。
1.1 SQL简介
JDBC最重要的功能是允许用户在Java程序中嵌入SQL语句,以实现对关系数据库的访问。本节中我们将介绍一些有关关系数据库的基本概念,并简单介绍SQL语言。
1.1.1 关系数据库简介
关系数据库系统的思想是由IBM公司的E.F.Codd于1970年率先提出的。
关系数据库支持的数据模型是关系模型。关系模型的基本条件是其关系模式中每个属性值必须是一个不可分割的数据量。简单地说,在关系数据库中数据结构一般是张两维表,这张两维表的每一列的值必须是不可分割的数据项,而任两行应互不相同。
关系模型的主要特点是:
⑴结构简单,表示力强,易于掌握。
⑵语言一体化,非过程化的操作,方便用户使用。
⑶有一定的数学理论作基础,利于进一步研究和发展。
⑷操作耗时,执行效率低。
关系数据库管理系统一般向用户提供数据检索、数据插入、数据删除、数据修改四种基本操作功能。
有关数据库的其它知识,若有需要,读者可以自己查阅相关书籍。
1.1.2 SQL语言简介
SYSTEM R是IBM公司的San Jose实验室研制的一个非常著名的关系数据库管理系统,SQL就是它所提供的数据库语言。SQL语言具有数据定义,数据操纵以及控制等功能。经过人们不断努力完善,1986年SQL语言被确定为关系数据库语言的国际标准。
SQL语言有两种使用方式:通过联机交互方式由终端用户作为语言使用,或作为子语言嵌入主语言中使用。JDBC允许用户在Java程序中嵌入SQL语言。
下面我们对SQL语言的各项功能进行简单介绍。
一、数据定义功能
主要包括定义基表、定义索引和定义视图三个部分。
1、基表的定义
⑴创建基表
用此语言可以定义基表,包括定义表名、域名、域类型及其它内容,形式如下:
CREATE TABLE (表名) (<域定义>[,<域定义>]...)[IN SEGMENT <段名>]
其中域定义形式如下:
<域名>(<数据类型>[,NONULL])
上面的定义中段名部分都可以不出现(缺省时表示指的是私有段)。NONULL出现时表示不允许空值出现。
例 我们可以创建一个表testTable,包括域id和name,创建方法如下:
CREATE TABLE testTable(id (Integer),name(Char 10))
⑵扩充基表
用它可在已有的基表中增添新的域。形式:
EXPEND TABLE <基表名> ADD FIELD (<定义域>)
例 在testTable中增加一个域age,方法如下
EXPEND TABLE testTable ADD FIELD (age (INTEGER))
⑶取消基表
用此语句可以取消指定的表。基本形式如下:
DROP TABLE <表名>
例 DROP TABLE testTable
将表testTable删除。
2、建立索引、取消索引
为了提供多种存取路径和一定条件下的快速存取,我们可以对基表建立若干索引。建立索引和取消索引的语句形式如下。
⑴建立索引
CREATE [UNIQUE] INDEX <索引名> ON <表名> ([<域名>[<顺序>]][,<域名>[<顺序>]]...)
其中若出现UNIQUE表示不允许两个元组在给定索引中有相同的值。<顺序>若为ASC表升序,为DESC为降序,缺省时为ASC。
例 CREAGE INDEX X ON testTable (id)
就表示在表testTable上建立一个按id升序排列的索引X。
⑵取消索引
DROP INDEX <索引名>
3、视图的定义
⑴创建视图
CREATE VIEW 视图名[(字段名[,字段名]...)] AS 子查询
⑵取消视图
DROP VIEW 视图名
二、数据操纵功能
主要包括Select语句、Insert语句、Delete语句和Update语句。
⑴Select语句
SQL中SELECT语句可以对数据库实现基本的查询功能。基本形式如下:
SELECT <[DISTINCT]<选择项>>|* FROM <表名>[,<表名>] [WHERE <条件>] [<GROUP BY 子句>[<Having子句>]] [<Order BY 子句>]
GROUP BY子句对映象中所得的元组集合按指定域分组,Having子句对GROUP BY设置一些逻辑条件。Order BY子句给出检索结构的顺序。
SQL还有一些简单的统计功能
①COUNT集合元素个数的统计;
②SUM集合元素的和;
③MAX(MIN):集合中的最大(最小)元素;
④AVG:集合元素的平均值。
此外SQL语言允许对多个数据库进行连接查询。下面我们举一个关于SELECT的简单例子。
例 对testTable进行检索,查询id=3的成员的name
SELECT name
FROM testTable
WHERE id=3
有关SELECT语句其它功能的使用,请读者自行查阅相关书籍。
⑵INSERT语句
用户使用INSERT语句可以实现对数据库增加记录的功能。
INSERT INTO <表名>[(字段名[,字段名]...)] VALUES (常量[,常量]...)
例 在testTalbe中插入id=6,name="Xu"的一条记录
INSERT INTO testTable(id,name) VALUES(6,'XU')
⑶DELETE语句
用户使用DELETE语句可以实现对数据库的基本删除功能。
DELETE FROM <表名> WHERE <条件>
例 在testTable中删除id=3的记录
DELETE FROM testTable WHERE id=3
⑷UPDATE语句
使用UPDATE语句可以实现对数据库的基本修改功能。基本形式如下:
UPDATE <表名> SET 字段=表达式[,字段=表达式]...WHERE <条件>
例 将表testTable中id=3的记录中name项尾部增加字符s
UPDATE testTable
SET name=name+'s'
WHERE id=3
1.2 JDBC概述
Java语言具有的健壮性、安全性、可移植性、易理解性及自动下载等特点,使它成为一种适用于数据库应用极好的基本语言。在此基础上建立的JDBC,为独立于数据库管理系统DBMS的应用提供了能与多个不同数据库连接的通用接口。
JDBC由一组Java语言编写的类和接口组成,使用内嵌式的SQL,主要实现三方面的功能:建立与数据库的连接,执行SQL声明以及处理SQL执行结果。JDBC支持基本的SQL功能,使用它可方便地与不同的关系型数据库建立连接,进行相关操作,并无需再为不同的DBMS分别编写程序。下面我们先介绍JDBC与SQL及ODBC的关系,再介绍JDBC支持的两种模型,最后介绍JDBC的抽象接口和数据库驱动器Driver。
1.2.1 JDBC与SQL
前一节中我们已经介绍过,SQL语言是关系型数据的标准操作语言。JDBC建立在SQL声明的基础上,从而在不同的数据库功能模块的层次上提供了一个统一的用户接口。JDBC是一个较低层的API接口,也就是说它直接执行SQL声明和取回执行结果,它是更高层次API的基础。高层的API,具有更为友善的用户界面,而将执行转交给JDBC这样的低层接口完成。
尽管一般数据库系统在很大范围内支持SQL的语义语法,但它们在复杂的高层次功能如存储功能调用和外部连接等方面往往不尽一致。为了解决这一矛盾,JDBC采用以下的几种措施:
(1)JDBC允许使用从属于DBMS的系统的任何询问语句,这些询问语句都将被传送给后台的DBMS实际执行。这样增加了使用的灵活性,一个应用程序的询问可以不是SQL形式的,而是SQL的特定引出形式,如为特定的DBMS提供的文件或图片查询。这样做的风险则在某些DBMS中可以会出现错误。
(2)一般认为ANSI SQL Entry Level的功能比较完备,而且是被广泛支持的。因此为了使JDBC与SQL一致,要求用户使用至少ANSI SQL 92 Entry Level以上的版本,这样就给那些要求广泛的可携带性的应用程序提供了共同命名的保证。
(3)对较复杂的应用,JDBC提供了接口类DatabaseMetadata,用户可通过这个类获取相应DBMS的描述信息,再根据所得信息采取特定的查询操作方式。
1.2.2 JDBC与ODBC
Microsoft的ODBC(Open DataBase Connectivity)是当前与关系型数据库连接最常用的接口。JDBC是建立在ODBC的基础上的,实际上可视为ODBD的Java语言翻译形式。当然两者都是建立在X/Open SQL CLI(Call Level Interface)的抽象定义之上的。而JDBC与ODBC相比,在使用上更为方便。
既然ODBC已经是成型的通用接口,我们可在Java程序中直接使用ODBC却要建立JDBC接口,这样做的原因和好处包括以下几点:
(1)ODBC使用的是C语言界面,而从Java直接调用C源码容易在安全性、健壮性和可移植性等方面产生问题,运行功效也受到影响。
(2)将ODBC的C语言API逐字译为Java也并不理想。比如,Java没有指针,ODBC的一种面向对象式的翻译界面,对Java的程序员来说更为自然方便。
(3)ODBC难于学习掌握,经常将简单的特性与复杂的特性混合使用。而JDBC相对简单明了许多,容易理解掌握。
(4)JDBC有助于实现“纯Java“的方案。当使用ODBC时,每一台客户机都要求装入ODBC的驱动器和管理器。而当使用JDBC,驱动器完全由Java语言编写时,JDBC代码可以在所有的Java平台上自动装入、移植,而且是安全的。
当然,在JDBC也可以使用ODBC,但是要通过中介JDBC-ODBC Bridge使用。
1.2.3 JDBC支持的两种模型
在与数据库的连接操作中,JDBC支持两种不同的模型。这两川模型根据用户与数据库的关系层次不同,分别称为两模型和三层模型。
两层模型中,Java的应用程序(Applet或Application)直接与数据库联系。用户的SQL声明被提交给数据库,执行的结果回送给用户,如图1.1所示。这种模型具有客户机/服务器结构,用户的机器如同客户机,存放数据库的机器则如同服务器,连接两者的可以是局域网,也可以是广域网。
┌─────────┐
│Java Application │
│ Java Applet │ Clinet Machine
├─────────┤
│ JDBC │
└─────────┘
↑
↓
┌─────────┐
│ DBMS │ Database Serve
└─────────┘
图1.1 JDBC支持的两层模型
在三层模型中,用户不直接与数据库联系。用户的命令首先发送给一个所谓“中间层”,中间层再将SQL声明发给DMBS。执行的结果也同样由中间层转交,如图1.2所示。三层模型的好处是,可以通过中间层保持对存取权限和公有数据允许修改类型的控制,便于安全管理。同时,用户可以使用一种较为友善的高层API,由中间层转化为恰当的低层命令,保证较好地运行功效。到目前为止,中间层多用C或C++语言编写。随着Java语言的逐步推广,将出现用Java编写的中间层,更好地利用它的健壮性、多线程,安全性等特点。
┌──────────┐
│ Java Applet │
│ HTML Browse │
└──────────┘
↑
↓
┌──────────┐
│Application Server │
│ (Java) │
├──────────┤
│ JDBC │
└──────────┘
↑
↓
┌──────────┐
│ DBMS │
└──────────┘
图1.2 JDBC支持的三层模型
1.2.4 JDBC的抽象接口
JDBC中最重要的部分是定义了一系列的抽象接口,通过这些接口,JDBC实现了三个基本的功能:建立与数据的连接、执行SQL声明和处理执行结果。主要的接口和功能实现关系如图1.3所示。
┌─────────────┐
│DriverManager│
└─────────────┘
↙ ↘
┌──────────┐┌──────────┐
│Connection││Connection│
└──────────┘└──────────┘
↙ ↘ ↘
┌─────────┐┌─────────┐┌─────────┐
│Statement││Prepared-││Callable-│
│ ││Statement││Statement│
└─────────┘└─────────┘└─────────┘
↑ ↑ ↑
↓ ↓ ↓
┌─────────┐┌─────────┐┌─────────┐
│ResultSet││ResultSet││ResultSet│
└─────────┘└─────────┘└─────────┘
图1.3 sql包中的主要接口和相互关系
这些接口都存在Java的sql包中,它们的名称和基本功能是:
*java.sql.DriverMagnager
管理驱动器,支持驱动器与数据连接的创建。
*java.sql.Connection
代表与某一数据库的连接,支持SQL声明的创建。
*java.sql.Statement
在连接中执行一静态的SQL声明并取得执行结果。
*java.sql.PreparedStatement
Statement的子类,代表预编译的SQL声明。
*java.sql.CallableStatement
Statement的子类,代表SQL的存储过程。
java.sql.ResultSet
代表执行SQL声明后产生的数据结果。
在下一节中,我们将对这些接口加以详细的介绍。
1.2.5 JDBC的数据库驱动器Driver
Java的应用程序员通过sql包中定义的一系列抽象类对数据库进行操作,而实现这些抽象类,实际完成操作,则是由数据库驱动器Driver运行的。它们之间的层次关系如图1.4所示
┌────────┐
│Java Application│
└────────┘
JDBC API ——————————————————————————————
┌──────┐
│JDBC Manager│
└──────┘
———————————————————————————————————
┌────┐┌───────┐┌─────┐┌────────┐
DJBC │JDBC-Net││JDBC-ODBC ││Native-API││Native-Protocol │
Drivers │Driver ││Bridge Driver ││Driver ││Driver │
└────┘└───────┘└─────┘└────────┘
↓ ↓ ↓ ↓
┌────────────────────────────────┐
│ D B M S │
└────────────────────────────────┘
图1.4 JDBC Drivers
JDBC的Driver可分为以下四种类型:
(1)JDBC-ODBC Bridge和ODBC Driver
这种驱动器器通过ODBC驱动器提供数据库连接。使用这种驱动器,要求每一台客户机都装入ODBC的驱动器。
(2)Native-API partly-Java Driver
这种驱动器将JDBC指令转化成所连接使用的DBMS的操作形式。各客户机使用的数据库可能是Oracle,可能是Sybase,也可能是Access,都需要在客户机上装有相应DBMS的驱动程序。
(3)JDBC-Net All-Java Driver
这种驱动器将JDBC指令转化成独立于DBMS的网络协议形式,再由服务器转化为特定DBMS的协议形式。有关DBMS的协议由各数据库厂商决定。这种驱动器可以联接到不同的数据库上,最为灵活。目前一些厂商已经开始添加JDBC的这种驱动器到他们已有的数据库中介产品中。要注意的是,为了支持广域网存取,需要增加有关安全性的措施,如防火墙等等。
(4)Native-protocol All-Java Driver
这种驱动器将JDBC指令转化成网络协议后不再转换,由DBMS直接使用。相当于客户机直接与服务器联系,对局域网适用。
在这四种驱动器中,后两类“纯Java”(All-Java)的驱动器效率更高,也更具有通用性。但目前第一、第二类驱动器比较容易获得,使用也较普遍。本书中的例程就都是用JDBC-ODBC Bridge驱动器完成的。
1.3 JDBC编程
本节我们将对JDBC编程进行具体的介绍。本节的程序中使用JDBC驱动器均为JDBC-ODBC Bridge,使用的数据库为ACCESS数据库。
1.3.1 程序基本结构
一般的JDBC程序都完成三项功能:与数据库建立连接;传送SQL 声明以及对返回结果进行处理。下面我们通过一个具体例子说明这三项功能的实现过程。
例1.1 Creage.java给出了一个简单的JDBC程序,此程序执行后创建一张名为testTable的表,表中包括两个域,域名分别为id和name。
例1.1 Create.java及程序说明。
1: import java.net.URL;
2: import java.sql.*;
3:
4: class Create{
5: public static void main (String[] args){
6: String url="jdbc:odbc:demo";
7: String query="CREATE TABLE testTable" + "(id INT,name CHAR(10))";
8:
9: try{
10: //下载jdbc-odbc bridge 驱动器
11: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句参见下面Cyclone的注释
//与驱动器建立连接
14: Connection con=DriverManager.getConnection(url,"user","password");
15:
16: //创建一个Statement对象
17: Statement stmt=con.createStatement();
18:
19: //执行SQL声明
20: stmt.executeUpdate(query);
21: System.out.println("Create successfully!");
22:
23: //关闭 stm
24: stmt.close();
25:
26: //关闭连接
27: con.close();
28: }catch(SQLException ex){
//SQL异常信息
29:System.out.println("\n***SQLException caught ***\n");
30: while(ex!=null){
31: System.out.println("SQLState:"+ex.getSQLState());
32: System.out.println("Message:"+ex.getMessage());
33: System.out.println("Vendor:"+ex.getErrorCode());
34: ex=ex.getNextException();
35: System.out.println("");
}
36:}catch(java.lang.Exception ex){
37:ex.printStackTrace();
38:}
39:}
40:}
41:
[Cyclone注:我编译本例用的是VJ++6.0,操作系统是Win98(2版),编译通过,但执行程序后报告“No suitable driver”,我判断出错原因是Win98与sun.jdbc.odbc.JdbcOdbcDriver没有协调好。此后,笔者把第11句换为:Class.forName("com.ms.jdbc.odbc.JdbcOdbcDriver");再编译运行通过。]
例1.1的基本功能是建立一个名为testTable的表,这张表有两个域id和name。程序结构很简单:
第一步,下载所使用的驱动器并与数据库建立连接(10~14)行;
第二步,创建一个Statement的实例,执行CREATE这一个SQL声明(20行);
第三步,处理结果。在本程序中仅简单地输出“Create successfully!”(21行);
第四步,关闭连接及Statement的对象(24~27行)。
程序中提供try-catch语句捕获SQL异常,发生异常时给出相应信息。
第一次运行Cteate.java屏幕显示“Create successfully!”若再运行一次Create.java程序,屏幕上将显示异常处理信息。运行结果如下所示。
第一次运行Create.java的结果:
C:\MyDemo\mydb>java Create
Create successfully!
第二次运行Create.java的运行结果:
C:\MyDemo\mydb>java Create
***SQLException caught***
SQLState:S0001
Message:[Microsoft][ODBC Microsoft Access 7.0 Driver] Table 'testTable' already exists.
Vendor:-1303
下面我们对程序中涉及到的问题作进一步的探讨。
一、建立数据源
在执行程序前我们必须先建立数据源。本节使用的数据库是ACCESS数据库。建立数据源的基本步骤如下:
(1)打开控制面板,选择ODBC项(如图1.15)(略)
(2)选择 Add...按钮(如图1.6)(略)
(3)选择Microsoft Access Driver项(如图1.7)(略)
(4)在Data Source Name中填入数据源的名称,在Discription中加入一些描述信息,按下Select...按钮可以将数据源与某个数据库文件相连,按下(Create...按钮可以创建一个新的数据库文件并把它和数据源相连。按钮OK按钮结束操作(如图1.8)。
二、加载驱动器(driver)
首先,例1.1中使用了方法Class.forName(String)来加载驱动器(driver)。
JDBC中提供了一个Driver接口,每个真正的驱动器必须实现这个接口。编写JDBC程序时必须先加载所使用的驱动器,也就是说必须创建一个Driver类的实例。Java提供了两种方式加载驱动器。
第一种加载驱动器的方式是使用方法Class.forName(String),如例1.1中所用的那样。这也是推荐大家使用的方法,因为使用这个方法可以随时明确加载驱动器而不需要外部安装调用这个方法后将对所指定驱动器类创建一个实例,并将这个实例作为参数,自动调用方法DriverManager.registerDriver,从而将这个实例加入DriverManager管理的驱动器表中。换句话说,就是对这个驱动器进行了注册,允许对其创建连接。
另一种加载驱动器的方式是将驱动器加入java.lang.Systemproperty "jdbc.drivers"。
图1.8 创建一个新的数据文件并把它和数据源相连(略)
例 你可以在~/.hotjava/properties文件中加入下面语句来指定所需加载的驱动器。jabc.drivers=foo.bah.Driver:wombat.sql.Driver;
在初始化时,DriverManager将寻找系统资源jdbc.drivers,并加载已在其中指定的驱动器,然后自动对这些驱动器进行注册,允许创建连接。
三、建立连接
加载了驱动器后,可以用DriverManager中的方法getConnection建立与特定数据库的连接。
方法如下:
public static synchronized Connection getConnection(String url,String user,String password) throws SQLException;
DriverManager将从已注册的JDBC驱动器集合中选择合适的驱动器,并返回与此驱动器相关的一个Connection类的实例。
在本节我们介绍URL的基本结构,在介绍JDBC的URL之前,先介绍普通的URL。
⑴URL(Uniform Resource Locator)是用于确定Internet上资源的相关信息,作用相当于一个地址。一般情况下URL由三部分确定:
①访问资源所使用的协议。一般有ftp(文件传输协议)、http(超文本传输协议)和file(本地文件系统)。它们的后面一般跟有冒号:这部分是必不可少的。
②主机信息。它给定了要访问的资源所在的主机的信息。所有Internet上的主机信息以“//”开始,而本地文件系统的主机信息以“/”开始。它们均以“/”结束。
③访问路径。
下面就是一个典型的Internet上的URL:
http://www.nju.edu.cn/incoming
它的含义是按http协议访问www.nju.edu.cn主机上名字为incoming的目录。
⑵JDBC的URL
JDBC的URL提供了标数据库的方法,从而使DriverManager可以用它找到合适的驱动器并建立连接。JDBC的URL的基本形式是:
jdbc:<subprotocol>:<subname>
①jdbc在JDBC的URL中是固定作为第一部分的。
②<subprotocol>:subprotocol是驱动器或数据库的连接机制,不同的驱动器可以支持同一个子协议。它们由驱动器的书写者提供。
③<subname>:用于确定特定数据库。如果访问的资源是在Internet网上,在subname中应包括网络地址,当然,网络地址的形式必须遵守标准的URL的命名规定。
在程序中我们使用的子协议(subprotocol)是ODBC,这是一个特殊的协议。使用ODBC子协议的URL的基本格式是:
jdbc:odbc:<data-source-name)[;attribute_name>=<attribute_value>]
在此URL格式中指定了ODBC风格的数据源的名字作为subname,并允许在其后附加其它例如password之类的属性参数。
在程序1.1中(第14行),
String URL="jdbc:odbc:demo";
Connection con=DriverManager.getConnection(url,"user","password");
建立了一个与数据源demo相联的连接。
DriverManager这个类为管理一组JDBC驱动器提供了最基本的服务,它除了用于数据库与对应驱动器之间的连接建立外,还对所提供的驱动器进行跟踪以及相关信息打印等服务。除了上面已解释过的方法getConnection()外,DriverManager还提供了其它方法:
① public static synchronized void registerDriver(Driver driver) throw SQLExceptin;
调用这个方法可以对新加载的驱动器进行注册,使其可被DriverManager管理。
② public static void setLogStream(PrintStream out)
设置DriverManager和其它驱动器使用的跟踪信息输出流。
例 在例1.1的class.forName("jdbc.odbc.JdbcodbcDriver");后加上语句
DriverManager.setLogStream(System.out);
后重新编译执行,读者会发现在屏幕上出现大量有关连接及其它操作的信息。
DriverManager还提供了很多其它方法管理驱动器,详细内容可以查阅JDBC的API。
四、传递SQL声明
在程序中建立了与数据库连接后,返回了一个Connection类的实例。通过这个实例可以与特定的数据库进行通信。
⑴Connection类提供了下列三种方法创建三类声明,这三类声明的具体功能有所不同,但基本功能都是传送并执行SQL声明,具体的内容将在下一节介绍。
三种方法:
public abstract statement createStatement() throws SQLException;
public abstract PreparedStatement prepareStatement(String sql) throws SQLException;
public abstract CallableStatement prepareCall(String sql) throws SQLExecption;
⑵被连接的数据库可以提供一些有关自身的信息,例如它所支持的SQL语法,表的描利用类DatabaseMetaDate中的方法可以获取相关信息。Connection类提供了获取与所连的数据库相应的DatabaseMetadata类的一个实例的方法。
方法是:
public abstract DatabaseMetaData getMetaData() throws SQLException;
⑶Connection类提供了其它相关方法。例如它提供了方法close()来关闭对特定数据库的连接,提供方法getWarnings()来获取SQL警告信息等。
在例1.1中,我们使用Connection类中的方法创建了一个类statement的实例,并传送且执行了一条用于创建一个名tesTable的表的SQL声明,最后关闭了连接。另外,例1.1还对SQL异常进行了处理。
五、结果处理
执行了SQL声明后会产生相应的结果,用户可以使用JDBC中提供的方法来获取结果并进行相应的处理。在后面几节中将会结合实例作进一步的介绍。
1.3.2 Statement类及其子类
JDBC提供了三种类将SQL声明传送给数据库。它们是:
⑴Statement:用于执行不带参数的简单的SQL声明。
⑵PreparedStatement:用于执行预编译的SQL声明,并允许在SQL声明中携带IN参数。
⑶Callablestatement:用于执行数据库存储过程的调用。它允许使用IN、OUT或INOUT三种类型的参数,这三个类的关系如下图所示:
Statement
│
│增加IN参数
└────→PreparedStatement
│
│增加OUT参数
└────→CallableStatement
图1.9 Statement之间的继承关系
一、类Statement
⑴类Statement对象的创建方法
Statement的对象用于执行静态SQL声明(即不带参数的SQL声明)并获取处理结果。
创建一个Statement类的实例的方法很简单,只需调用类Connection中的方法createStatement()就可以了。一般形式如下:
Connection con=DriverManager.getConnection(URL,"USER","password")
Statement stmt=con.createStatement();
⑵三种执行方法
创建了Statement类的实例后,可调用其中的方法执行SQL声明,JDBC中提供了三种执行方法,它们是execute(),executeQuery(),executeUpdate()。
①executeUpdate方法
public abstract int executeUpdate(String sql) throw SQLException.
这个方法一般用于执行SQL的INSERT、UPDATE或DELETE声明,或者执行无返回值的SQL DDL声明(即SQL数据定义语言),例如CREATE或DROP声明等。
当执行INSERT等SQL声明时,此方法的返回值是执行了这个SQL声明后所影响的记录的总行数。若返回值为0,则表示执行未对数据库造成影响。
若执行的声明是SQL DDL(Data Definition Language)声明时,返回值也是0。
②executeQuery方法
public abstract ResultSet executeQuery(String sql)throw SQLExecption;
这个方法一般用于执行SQL的SELECT声明。它的返回值是执行SQL声明后产生的一个ResultSet类的实例。利用ResultSet类中的方法可以查看相应用结果。
③execute方法
public abstract boolean execute(String sql) throw SQLException;
这个方法比较特殊,一般只有在用户不知道执行SQL声明后会产生什么结果或可能有多种类型的结果产生时才会使用。例如,执行一个存储过程(stored procedure)时,其中可能既包含DELETE声明又包含了SELECT声明,因而执行后,既产生了一个ResultSet,又影响了相关记录,即有两种类型的结果产生,这时必须用方法excute()执行以获取完整的结果。execute()的执行结果允许是产生多个ResultSet,或多条记录被影响或是两者都有。
由于执行结果的特殊性,所以对调用execute()后产生的结果的查询也有特定方法。
execute()这个方法本身的返回值是一个布尔值,当下一个结果为ResultSet时它返回true,否则返回false。
在Statement类中提供了getResultSet,getUpdateCount,getMoreResult等方法来查询执行execute()的结果。
* public abstract ResultSet getResultSet() throws SQLException
若当前结果是ResultSet,则返回一个ResultSet的实例,否则返回null,对每个结果而言,此方法只可调用一次,即每个结果只可被获取一次。
public abstract int getUpdateCount() throws SQLException
若当前结果是对某些记录作了修改,则返回总共修改的行数,否则返回-1。同样,每个结果只能调用一次这个方法。
* public abstract boolean getMoreResults() throw SQLException
此方法将当前结果置成下一个结果,使行得可以用方法getResultSet()和getUpdateCount()进行查询。
当下一个结果是ResultSet时返回true,否则返回false。
下面给出一个程序Insert.java。其中使用了executeUpdate()执行SQL声明,在下节介绍ResultSet时,我们将讨论用executeQuery()执行SQL声明。至于用execute()执行SQL声明并获取相应结果,由于它一般只用于处理存储过程,因此我们将在介绍CallabeStatement时进行介绍。
例1.2 Insert.java及程序说明
1: import java.net.URL;
2: import java.sql.*;
3:
4: class Insert{
5: public static void main(String args[]){
6: String url="jdbc:odbc:demo";
7: try{
//下载jdbc-odbc bridge 驱动器
8: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//参见例1.1的第11句的注释
9: //与驱动器建立连接
10: Connection con=DriverManager.getConnection(url,"user","password");
//创建一个Statement对象
11: Statement stmt=con.createStatement();
12: //执行SQL声明
13: int count1=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(1,'wu')");
14: int count2=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(2,'wang')");
15: //打印执行结果
16: System.out.println("Insert successfully!");
17: System.out.println("Updated rows is"+(count1+count2)+".");
//关闭连接
18: stmt.close();
19: con.close();
20: }catch(SQLException ex){
//打印SQL异常信息
21: System.out.println("\n***SQLException caught ***\n");
22: while(ex!=null){
23: System.out.println("SQLState:"+ex.getSQLState());
24: System.out.println("Message:"+ex.getMessage());
25: System.out.println("Vendor:"+ex.getErrorCode());
26: ex=ex.getNextException();
27: System.out.println("");}
28: }catch(java.lang.Exception ex){
29: ex.printStackTrace();
30: }
31: }
32: }
程序Insert.java完成的功能是往已建的表testTable中增加两条记录。
类似于例1.1,在Insert.java中首先对数据库建立连接,再创建一个Statement的对象执行INSERT这一SQL声明,最后输入处理结果。在程序中使用的是方法excuteUpdate(String),它一般用于执行INSERT,DELETE,UPDATE及SQL DDL声明,它的返回结果为影响的记录的总行数。运行Insert.java后,结果如下所示。读者可调用下一节中的Select.java程序查看testTable中的内容,以了解执行INSERT声明对表产生的作用。
程序Insert.java运行结果。
C:\MyDemo\mydb>java Insert
Insert successfully!
Updated rows is 2
C:\MyDemo\mydb>java Select
The detail of testTable is;
id,name
1,wu
2,wang
二、类PreparedStatement
PreparedStatement类是Statement类的子类,它直接继承并重写了Statement的方法。PrepardStatement类有两大特点:
特点一:一个PreparedStatement的对象中包含的SQL声明是预编译的,因此当需要多次执行同一条SQL声明时,利用PreparedStatement传送这条SQL声明可以大大提高执行效率。
特点二:PreparedStatement的对象所包含的SQL声明中允许有一个或多个IN参数。创建类PreparedStatement的实例时,IN参数用“?”代替。在执行带参数的SQL声明前,必须对“?”进行赋值,为了对“?”赋值,PreparedStatement类中增添了大量的setXXX方法,完成对IN参数赋值。
⑴创建PreparedStatement对象
与创建Statement类的实例方法类似,创建一个PreparedStatement类的对象也只需在建立连接后,调用Connection类中的方法
public abstract PreparedStatement prepareStatement(String sql) throws SQLException;
例 创建一个PreparedStatement的对象,其中包含一条带参数的SQL声明。
PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable(id,name) VALUES(?,?)");
⑵IN参数的赋值
PreparedStatement中提供了大量的setXXX方法对IN参数进行赋值。根据IN参数的SQL类型应选用合适的setXXX方法。
例 对上例,若需将第一个参数设为3,第二个参数设为XU,即插入的记录id=3,name="XU",可用下面的语句实现:
pstmt.setInt(1,3);
pstmt.setString(2,"XU");
除了setInt,setLong,setString,setBoolean,setShort,setByte等常见的方法外,PreparedStatement还提供了几种特殊的setXXX方法。
①setNull(int ParameterIndex,int sqlType)
这个方法将参数值赋为Null。sqlType是在java.sql.Types中定义的SQL类型号。
例 语句
pstmt.setNull(1,java.sql.Types.INTEGER);
将第一个IN参数的值赋成Null。
②setUnicodeStream(int Index,InputStream x,int length);
setBinaryStream(int Index,inputStream x,int length);
setAsciiStream(int Index,inputStream x,int length);
当参数的值很大时,可以将参数值放在一个输入流x中,再通过调用上述三种方法将其赋于特定的参数,参数length表示输入流中字符串长度。
下面给出了一个使用类PreparedStatement的程序。
例1.3 Insert2.java及程序说明
1:import java.net.URL;
2:import java.sql.*;
3:
4:class Insert2{
5: public static void main(String args[]){
6: String url="jdbc:odbc:demo";
7: String data[][]={{"5","xu"},{"6","yan"}};
8: try{
//下载jdbc-odbc bridge 驱动器
9: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句请参见例1.1的11句注释
10: //与驱动器建立连接
11: Connection con=DriverManager.getConnection(url,"user","password");
12: //创建一个ParepareStatement对象
13: PreparedStatement pstmt=con.prepareStatement(
14: "INSERT INTO testTable (id,name) VALUES(?,?)");
15: //参数赋值,执行SQL声明
16: for (int i=0;i<data.length;i++){
17: pstmt.setInt(1,Integer.parseInt(data[i][0]));
18: pstmt.setString(2,data[i][1]);
19: pstmt.executeUpdate();
20: }
21: System.out.println("Insert successfully!");
22: //关闭连接
23: pstmt.close();
24: con.close();
25: }catch(SQLException ex){
//打印SQL异常信息
26: System.out.println("\n***SQLException caught ***\n");
27: while(ex!=null){
28: System.out.println("SQLState:"+ex.getSQLState());
29: System.out.println("Message:"+ex.getMessage());
30: System.out.println("Vendor:"+ex.getErrorCode());
31: ex=ex.getNextException();
32: System.out.println("");}
33: }catch(java.lang.Exception ex){
34: ex.printStackTrace();
35: }
36: }
37:}
程序Insert2.java使用PreparedStatement来执行一个预编译的带IN参数的SQL声明:
INSERT INTO testTable(id,name) VALUES(?,?)
这一SQL声明中带有两个IN参数,用?代替。
程序中(17~18)
pstmt.setString(2,data[i][l]);
这两条语句使用了setXXX方法对IN参数赋值。
与程序Insert.java一样,Insert2.java的功能也是向表testTable中增加了两条新的记录,同样,INSERT这一SQL声明也被执行了若干次。由于PreparedStatement允许执行预编译的SQL声明,所以程序Insert2.java中INSERT声明在执行前被预编译了,多次执行时就体现了其高效的优越性。另外,正是由于PreparedStatement允许执行带IN参数的SQL声明,因此才可能多次执行同一条INSERT声明而插入值不同的记录。
程序Insert2.java的运行结果如下,调用下节的Select.java程序可查看表中内容。
程序Insert2.java的运行结果。
C:\MyDemo\mydb>java Insert2
Insert successfully!
C:\MyDemo\mydb>java Select
The detail of testTable is;
id,name
1,wu
2,wang
5,xu
6,yan
程序Insert2.java体现了类PreparedStatement的两大特点:
⑴它执行的INSERT语句中带有两个IN参数;
⑵INSERT这一SQL声明被预编译了,因此多次执行效率较高。
三、类CallableStatement
CallableStatement为在JDBC程序中调用数据库的存储过程提供了一种标准方式。在JDBC中激活一个存储过程的语法为:
{call proc_name(?,?,...?)}
或{?=call proc_name(?,?,...?)}
另外,CallableStatement中调用的存储过程允许带有IN参数、OUT参数或INOUT参数,所以作为PreparedStatement的子类。CallableStatement除了继承了PreparedStatement中的方法外,还增加了处理OUT参数的方法。
⑴创建CallableStatement的对象
创建CallableStatement的对象主要用于执行存储过程,所以读者必须先弄清你的数据库是否支持存储过程。读者可以使用DatabaseMetaData类中的有关方法去获取相关信息(详见DatabaseMetaData一节)。
调用类Connection中的方法:
public abstract CallableStatement prepareCall(String sql) throws SQLException;
可以创建一个CallableStatement的对象
例 CallableStatement cstmt=con.prepareCall("{call test(?,?)}");
⑵参数值设定与获取
CallableStatement类继承了PreparedStatement类中的setXXX方法对IN参数进行赋值,对OUT参数,CallableStatement提供方法进行类型注册和检索其值。
①类型注册的方法
在执行一个存储过程之前,必须先对其中的OUT参数进行类型注册,当你使用getXXX方法获取OUT参数的值时,XXX这一Java类型必须与所注册的SQL类型相符。
CallableStatement提供两种方法进行类型注册:
* registerOutParamenter(int parameterIndex,int sqlType);
* registerOutParameter(int parameterIndex.,int sqlType,int scale);
第一种方法对各种类型注册均适用,除了Numeric和Decimal两种类型。对这两种类型一般用第二种方法进行注册,第二种方法中的参数scale是一个大于等于零的整数,这是一个精度值,它代表了所注册的类型中小数点右边允许的位数。
例 下面的例子就对一个名为test的存储过程中的OUT参数进行类型注册:
cstmt.registerOutParameter(2,java.sql.Types.VARCHAR);
这条语句对test这一存储过程中的第二个参数进行输出类型注册,注册的SQL类型为java.sql.Types.VARCHAR。
②查询结果的getXXX方法
由于CallableStatement允许执行带OUT参数的存储过程,所以它提供了完善的getXXX方法来获取OUT参数的值,获取的值为SQLNull时,各个方法的返回值略有不同。
* boolean wasNull()
当获取的参数值为Null时,返回true。
* getBoolean(int)
当获取值为Null时,返回true。
* getByte(int),getLong(int),getInt(int),getFloat(int),getDouble(int),getShort(int)
当参数值为SQLNull时,返回0。
* getString(int),getNumeric(int,int),getBytes(int),getDate(int),getTime(int)
当参数值为SQLNull时,返回值为null。
在CallableStatement类中,除了IN参数与OUT参数外,还有一种INOUT参数。INOUT参数具有其它两种参数的全部功能,可以用setXXX方法对参数值进行设置,再对这个参数进行类型注册,允许对此参数使用getXXX方法。执行完带此参数的SQL声明后,用getXXX方法可获取改变了的值。当然,在进行类型注册时,要考虑类型一致性问题(有关类型一致性将在后面的章节中讨论)。
CallableStatement一般用于执行存储过程,所以执行结果可能不单一——即可能为多个ResultSet,或多次修改记录或两者都有。所以对CallableStatement一般调用方法execute()执行SQL声明。
下面给出了例1.4 CallAble.java,其中使用了CallableStatement,并执行了execute()方法。
程序中使用了两上带参数的储存过程Serch(?)和Delete(?),这两个储存过程是在ACCESS中定义的。它们的SQL语句如下:
⑴Search(?)
PARAMETERS IDofStudent Long;
SELECT DISTINCTROW Students.*
FROM Students
WHERE ((Students.StudentID=[IDofStudent]));
⑵Delete(?)
PARAMETERS IDofStudent Long;
DELETE *
FROM Students
WHERE ((Students.StudentID=IDofStudent));
在ACCESS数据库中创建一个储存过程很容易。首先,打开与使用数据源相连的数据库(DataBase)(两者的关系在1.3.1.1中已经介绍过了);然后,如图1.10所示,选择Query项后,选择按钮New,此时出现两个选择,选择New Query项后(如图1.11)就可以编制储存过程了。此时选择工具条上的SQL按钮,可以实现用SQL语言手工编制储存过程。
例1.4 Callable.java。
1:import java.net.URL;
2:import java.sql.*;
3:
4:class Callable{
5:public static void main(String args[]){
6:String url="jdbc:odbc:test";//注意这里不再是demo数据源
7:try{
8://下载jdbc-odbc bridge 驱动器
9:Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句请参见例1.1的11句注释
10:
11://建立连接
12:Connection con=DriverManager.getConnection(url,"my-user","my-password");
15://创建一个CallableStatement用于执行储存过程
16:CallableStatement cstmt=con.prepareCall("{call Search(?)}");
17://参数赋值
18:cstmt.setInt(1,934678);
19://执行储存过程。
20:cstmt.execute();
//处理执行结果
21:display(cstmt,"Search");
23:CallableStatement stmt=con.prepareCall("{call Delete(?)}");
24:stmt.setInt(1,934655);
25:stmt.execute();
26:display(stmt,"Delete");
//关闭连接
stmt.close();
cstmt.close();
con.close();
}
catch(SQLException ex){
//打印SQL异常信息
System.out.println("\n***SQLException caught ***\n");
while(ex!=null){
System.out.println("SQLState:"+ex.getSQLState());
System.out.println("Message:"+ex.getMessage());
System.out.println("Vendor:"+ex.getErrorCode());
ex=ex.getNextException();
System.out.println("");
}
}
catch(java.lang.Exception ex){
ex.printStackTrace();
}
}
//处理执行储存过程的结果
47:private static void display(CallableStatement cstmt,String name)
throws SQLException{
System.out.println("Excute procedure"+name);
while(true){
//处理情况为:执行结果影响了记录
51:int rowCount=cstmt.getUpdateCount();
if(rowCount>0){
System.out.println("Updated rows is"+rowCount+".");
//判断是否还有等待处理的结果
if(!cstmt.getMoreResults())break;
continue;
}
//处理情况为:执行结果为ResultSet
57:ResultSet rs=cstmt.getResultSet();
if(rs!=null){
ResultSetMetaData rsmd=rs.getMetaData();
//打印结果集的标题
int numCols=rsmd.getColumnCount();
for(int i=1;i<=numCols;i++)
System.out.println("\t\t"+rsmd.getColumnLabel(i)+" ");
System.out.println();
//打印结果集的内容
boolean more = rs.next();
if(!more) System.out.println("\t\t0 \t\t\t0 \t\t0");
while(more){
for(int i=1;i<=numCols;i++)
System.out.print("\t\t"+rs.getString(i)+" ");
more=rs.next();
System.out.println();
}
//判断是否还有等待处理的结果
72:if(!cstmt.getMoreResults()) break;
continue;
}
//处理情况为:执行结果影响了0条记录
if(rowCount==0)
System.out.println("No row is updated!");
if(!cstmt.getMoreResults()) break;
}
79:}
}
运行结果:
C:\MyDemo\mydb>java Callable
Execute procedure Search
ID StudentID FirstName
6 934678 li
15 934678 zhao
Execute procedure Delete
Updated rows is 1
例1.4中创建了两个CallableStatement的对象来执行两个带参数的储存过程Search(?)和Delete(?)(15~26行)。
方法display()(47~79行定义)用于处理执行储存过程后产生的结果,其中使用到的有关方法在介绍Statement时已经叙述,如:
51:int rowCount=cstmt.getUpdateCount(); //获取执行过程后影响的记录数
57:ResultSet rs=cstmt.getResultSet();//获取执行过程后得到的记录数
72:if(!cstmt.getMoreResults()) break;//判断是否还有等待处理的结果
至于其中有关结果信(ResultSet)的方法,我们将在下一节中详细介绍。
1.3.3 结果集ResultSet
一、基本概念和方法
结果集ResultSet是用来代表执行SQL声明后产生的结果集合的抽象接口类。它的实例对象一般在Statement类放其子类通过方法execute或executeQuery执行SQL语句后产生,包含有这些语句的执行结果。ResultSet的通常形式类似于数据库中的表,包含有符合查询要求的所有行中的指定行,例如假定我们已建立如下的一张表Student:
Table Student:
id name score
——————————————
1 Xu 90
2 Li 70
3 Wu 85
4 Wang 80
5 Zhang 88
对表Student执行如下的查询语句,并通过ResultSet取得结果:
ResultSet rs=stmt.executeQuery("SELECT id,name FROM student WHERE score>=85);
则所得到的结果集实例rs包括以下的内容:
id name //列标题
————————————————
rs: 1 Xu //列值
2 Wu
3 Zhang
由于一个结果集可能包含有多个符合要求的行,为了读取方便,使用读指针(cursor)来标记当前行。指针的初始位置指向第一行之前,ResultSet类中提供如下的next方法来移动指针,每调用一次next方法,指针下移一行:
public abstract boolean next()
当指针所指已经是结果集最后一行时,再调用next方法,返回值为false,表明结果集已处理完毕。因而,通常处理结果集的程序段具有下面的结构:
while(rs.next())
{...
//处理每一结果行
}
要注意的是,结果集的第一条也要在第一次调用next方法后才能取到。
在取得当前行后,ResultSet类通过一系列的“get”方法提供从当前行获得指定列的列值的手段。这一系列方法的形式为
get+列值类型(列名)
get+列值类型(列序号)
其中列名和列序号都用来指定要获取值的列。如我们前面创建的rs对象可从执行下面的语句来取值:
rs.getInt("id");
rs.getString("name");
也可以等价地执行
rs.getInt(1);
rs.getString(2);
在一行中,各列的值可以任意顺序读取。但为了保持最大的可移植性,通常是从左至右取值,且每列只读取一次。当一个结果集中可能有两个或两上以上列同名时,最好使用列序号来指定所需的列。列序号也是从左至右编号,以序号1开始。
对应get方法指定的类型,JDBC Driver总是试图将数据库实际定义的数据类型转化为适当的Java定义的类型。例如,对数据库的VARCHAR类型数据执行getString方法,将返回Java的String类对象。ResultSet还提供了一个极度为有用的方法getObject。它可以将任意数据类型返回为Java的Object类对象,对于获取 数据库特定的抽象类和编写通用程序都很有效。具体的类型对应转换这里不再多说,请读者参看1.3.5有关内容。
此外,ResultSet中比较有用的方法还有:
■public abstract int findColumn(String Columnname)
该方法的功能是根据所给出的结果集列名找出对应的列序号。
■public abstract boolean wasNull()
该方法的功能是检查最新读入的一个列值是否为SQL的空(Null)类型值。注意要首先用“get”方法读入某列,再调用本方法来检查是否为空。
■public abstract void close()
该方法的功能是关闭结果集。一般情形下,结果集无需显式关闭。当产生结果集的声明类对象关闭或再次执行时将自动关闭相应的结果集。当读取多个结果集的下一结果时,前一个结果集也将自动关闭。但在某些特定情况下,需要强制关闭以及进释放资源。
二、RusultSetMetaData
在大多数情况下,我们对于获得结果集的形式和结构应当是十分清楚的。但有时也可能在获得结果前我们对结果集不甚了解,此时就需要用到ResultSet的辅助类ResultSetMetaDa来获得对结果集的列的数目、列值类型和其它特性的描述性信息。这个类在编写通用性的数据库操作程序时也是十分有用的。
ResultSetMetaData 的对象通过ResultSet的方法getMetaData获得。比如假设rs是已获得的结果集,可以用下面语句来获得该结果集对应的信息描述对象:
ResultSetMelaData rmd=rs.getMetaData();
ResultSetMetaData类提供了大量有用的方法以获取有关结果集的信息。这些方法主要包括:
* Public abstract int getColumnCount()
该方法的功能是获得结果集的总列数。
* public abstract int getColumnDisplaySize(int column)
该方法的功能是获得指定序号代表的列的最大长度(以字符表示)。
* Public abstract String getColumnLable(int column)
该方法的功能是获得指定序号列用于显示时的建议标题。
* public abstract String getColumnName(int column)
该方法的功能是获得指定序号列的名称。
* public abstract int getColumnType(int column)
该方法的功能是获得指定序号列对应于包sql类Types定义的类的相应整数值。
* public abstract String getColumnTypeName(int column)
该方法的功能是获得指定序号列在数据源中特定的SQL类名。
* public abstract boolean lsReadOnly(int column)
该方法的功能是检查指定序号列是否只可读不可写。若为只可读的列,返回值 true;否则返回值false。
* public abstract boolean isDefinitelyWritable(int column)
该方法的功能是检查指定序号列是否一定可写。
* public abstract int getPrecision(int column)
该方法的功能是通过返回列值允许的小数位个数获得指定序号列的精度。
三、实例Select.java
这里我们给出一个例子程序 Select.java,程序的功能是对例1.1中创建的表 testTable进行查询,执行SQL操作:
SELECT * FROM testTable
然后将查询结果逐行显示。
例1.5 Select.java程序文件。
import java.net.URL;
import java.sql.*;
public class Select{
public static void main(String args[]){
String url = "jdbc:odbc:demo";
String query = "SELECT * FROM testTable";
try{
//装入驱动器jdbc-odbc bridge driver
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//建立连接
Connection con=DriverManager.getConnection(url,"user","passwd");
//创建声明对象
Statement stmt=con.createStatement();
//发出查询要求,获得结果集
ResultSet rs=stmt.executeQuery(query);
//显示结果集各行各列
System.out.println("The detail of testTable is:");
ResultSetMetaData rsmd=rs.getMetaData();
//获得结果集列数
in numCols=rsmd.getColumnCount();
//显示列标题
for(int i=1;i<=numCols;i++){
if(i>1) System.out.print(",");
System.out.print(rsmd.getColumnLabel(i));
}
System.out.println("");
//显示结果集信息
while(rs.next()){
//显示一行
for(int i=1;i<=numCols;i++){
if(i>1) System.out.print(",");
System.out.print(rs.getString(i));
}
System.out.print("");
}
//关闭结果集
rs.close();
//关闭声明
stmt.close();
//关闭连接
con.close();
}catch(SQLException ex){
//处理异常
System.out.print("\n * * * SQLException caught * * *\n");
while(ex != null){
System.out.println("SQLState:"+ex.getSQLState());
System.out.println("Message:"+ex.getMessage());
System.out.println("Vendor:"+ex.getErrorCode());
ex = ex.getNextException());
System.out.println("");
}
}catch(java.lang.Exception ex){
ex.printstackTrace();
}
}
在这个程序中,我们创建了一个Select类,它的唯一方法是main方法。在main方法中,定义了两个String常量:url和query。其中url用来存放数据源地址,query则用来存放要执行的SQL操作。这样的编程风格易于查错和修改,在后面的语句中只要直接引用就可以了。
接着,我们按照惯例装入驱动器,建立连接并创建SQL声明对象:
10:Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
12:Connection con=DriverManager.getConnection(url,"user","password");
15:Statement stmt= con.createStatement();
而SQL查询操作的执行和结果的返回,则通过下面的语句实现:
17:ResltSet rs=stmt.executeQuer(query);
此时rs中存放有查询结果集。
接下来的程序段 19~40行用来逐行逐列显示结果集rs的内容。首先我们创建rs相应的结果集信息对象rsmd:
21:ResultsSetMetaData rsmd=rs.getMetaData();
然后,通过rsmd获得结果集rs的列数:
23:int numCols=rsmd.getColumnCount();
在接下来显示rs的列标题时,我们还用到了rsmd的另一方法getColumnLabel(),以获取各列的显示标题:
28:System.out.print(rsmd.getColumnLable(i));
显示列标题之后,我们用while循环与 rs.next()方法配合,逐行显示表 testTable的各行数值。在这里,当前行的每一列数值是用ResultSet的getString方法获得的。尽管testTable的id到数值类型为INT,但JDBC会负责将其转化成Java的String类,以方便输出:
37: System.out.Print(rs.getString(i));
最后,是将结果集rs,声明stmt和连接con逐个关闭(行42~4干)。当捕获SQLException异常时,同前面的例子一样显示出错信息。若有其它类型异常发生,则显示栈轨迹(行47~57)。
程序Select.java执行的结果实际上是显示出整个testTable表的内容,输出如下。
例1.6 Select.java的运行结果。
C:\MyDemo\mydb>java Select
The detail of testTable is:
StudentID,FirstName
2,li
3,wang
C:\MyDemo\mydb>
1.3.4 DatabaseMetaData
一、功能简介
与ResultSetMetaData类相似,DatabaseMetaData是具有辅助性质的数据库信息类。它向用户提供了获取所使用的驱动器及数据库的有关信息的方法。DatabaseMetaData类的实例对象是通过Connection类的getMetaData方法创建的。例如下面的语句就创建了一个名为dma的DatabaseMetaData类对象:
Connection con=DriverManager.getConnection( url,"user","password");
DatabaseMetaData dma=con.getMetaData();
DatabaseMetaData提供大量获取信息的方法,这些方法可分为两大类:一类返回值为boolean型,多用以检查数据库或驱动器是否支持某项功能;另一类则用获取数据库或驱动器本身的某些特征值,返回值可能为整型,可能为字符串型,甚至可能是Resultset类的对象。下面就分别加以介绍。
二、常用方法(一)
在返回值为boolean类型的方法中,比较重要和常用的有:
* public abstract boolean allProceduresAreCallable()
该方法的功能是检查由getProcedures方法返回的过程是否都可被当前用户调用,若是, 返回 true。
* public abstract boolean useLocalFiles()
该方法的功能是检查数据库存储表的方式,当数据库使用本地机文件存放表时返回true。
*pubile abstract boolean isReadOnly()
该方法的功能是检查数据库,当数据库处于只读模式时返回true。
* public abstract boolean supportsGroupBy()
该方法的功能是检查数据库,当数据库支持GroupBy子句时返回true。
* public abstract boolean supportsMultipleResultSets()
该方法的功能是检查数据库和驱动器,当它们支持多个结果集时,返回true,否则返回false。
* public abstract boolean supportsANSI92EntryLevellSQL()
该方法的功能是检查数据库,当数据库支持ANSI92 Entry Level语法时返回 true。JDBC一致性要求所用的数据库都应支持这一语法。
* public abstract boolean supportsANSI92FullSQL()
该方法的功能是检查数据库,当它支持所有的 ANSI92 SQL语法时返回true,否则返回false 。
* public abstract boolean supportsOuterJolns()
该方法的功能是检查数据库,当它支持外部连接时返回true,否则返回false。
* public abstract boolean supportsStoredProcedures()
该方法的功能是检查数据库,当它支持存储过程,可以使用有关过程的逃逸语法时返回true,否则返回false。
三、常用方法(二)
在检查数据库和驱动器特性值的方法中,我们也只列出一些较重要和常用的,其余细节请读者参阅Java的API。
* public abstract StringgetURL()
该方法的功能是返回用于连接数据库的URL地址。
* public abstract String getUserName()
该方法的功能是获取当前用户名。
* public abstract String getDatabaseProductName()
该方法的功能是获取使用的数据库产品名。
* public abstract String getDatabaseProductVerslon()
该方法的功能是获取使用的数据库版本号。
* public abstract String getDriverName()
该方法的功能是获取用以连接的驱动器名称。
* public abstract String getProductVerslon()
该方法的功能是获取用以连接的驱动器版本号。
* public abstract String getSQLKeywords()
该方法的功能是获取数据库中非ANSI 92语法的关键字,以用逗号分隔的形式列出。
* public abstract String getProcedureTerm()
该方法的功能是获取数据库厂商对存储过程的特定称呼(term)。
* public abstract String getMaxColumnnameLength()
该方法的功能是获取数据库中定义的列名的最大允许长度。
* public abstract int getMaxStatements()
该方法的功能是获取允许同时打开的SQL声明的最大个数。
* public abstract int getMaxRowSize()
该方法的功能是获取一行允许的最大长度。
* public abstract ResultSet getProcedures
(String catalog,String schemaPattern,String procedureNamePattera)
该方法的功能是获取指定种类、指定模式,符合指定过程名模板的所有存储过程。
* public abstract ResultSet getTypeInfo()
该方法的功能是获取数据库中可能取得的所有数据类型的描述。
四、程序实例
下面我们给出一个使用DatabaseMetaData的实例DBMeta.java。在这个程序中,我们不创建任何SQL声明对象,也不执行任何SQL操作,只是建立与数据库的连接,然后通过DatabaseMetaData类获取一些有关数据库和驱动器的信息。
例1.7 DBMeta.java
import java.net.URL;
import java.sql.*;
public class DBMeta
{
public static void main(String args[])
String url = "jdbc:odbc:demo";
try
{
//装入驱动器jdbc-odbc bridge driver
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
//建立连接
Connection con=DriverManager.getConnection(url,"user","passwd");
//获取DatabaseMetaData对象
DatabaseMetaData dma = con.getMetaData();
//驱动器和URL信息
System.out.println("\nConnected to" + dma.getURL());
System.out.println("Driver" + dma.getDriverName());
System.out.println("Version" + dma.getDriverVersion());
//数据库信息
System.out.println("\nDataBase name:" + dma.getDatabaseProductName()+dma.getDatabaseProductVersion());
System.out.println("DataBase supports SQL keywords:\n\t" + dma.getSQLKeywords());
//数据库功能信息
System.out.print("\nDataBase supports ANSI92 Entry Level SQL:");
if(dma.supportsANSI92EntryLevelSQL())
System.out.println("YES");
else
System.out.println("NO");
System.out.print("DataBase supports ANSI92 Full SQL SQL:");
if(dma.supportsANSI92FullSQL())
System.out.println("YES");
else
System.out.println("NO");
System.out.print("DataBase supports Stored Procedure:");
if(dma.supportsStoredProcedures())
System.out.println("YES");
else
System.out.println("NO");
System.out.print("DataBase supports Outer Join: ");
if(dma.supportsOuterJoins())
System.out.println("YES");
else
System.out.println("NO");
//关闭连接
con.close();
}
catch(SQLException ex)
{
//处理异常
System.out.print("\n * * * SQLException caught * * *\n");
while(ex != null)
{
System.out.println("SQLState:"+ex.getSQLState());
System.out.println("Message:"+ex.getMessage());
System.out.println("Vendor:"+ex.getErrorCode());
ex = ex.getNextException());
System.out.println("");
}
}
catch(java.lang.Exception ex)
{
ex.printstackTrace();
}
}
首先,我们仍需要装入驱动器并建立与数据库的连接(行8~13)。接下来,创建一个DatabaseMetaData的对象: 16:DatabaseMetaData dma=con.getMetaData();
然后,我们可以利用这个对象分别获得联接的URL信息:
19:dma.getURL()
使用的驱动器名和版本号:
20:dma.getDriverName()
21:dma.getDriverVersion()
使用的数据库名:
24:dma.getDatabaseProductName()
数据库使用的特定关键字:
25:dma.getSQLKeywords()
然后还可以利用 dma检查数据库:是否支持ANSI 92 Entry Level SQL语法,是否支持
ANSI92全部SQL语法,是否支持存储过程及是否支持外部连接:
29:dma.supportsANSI92EntryLevelSQL()
34:dma.supportsANSI92FUllSQL()
39:dins.SupportsStoredProcedures()
44:dma.supportsOuterJoins()
并相应输出“YES”或“NO”。下面是通过JDBC-ODBC Driver对Access数据库连接时执行
DBMeta程序的结果。
例1.8 DBMeta的执行结果。
C:\MyDemo\mydb>java DBMeta
connected to jdbc:odbc:test//数据源
Driver JDBC-ODBC Bridge(ODBCJT32.DLL)//数据库驱动器
Version.1001(3.40.2728)八驱动器版本号
DataBase name:ACCESSZ.0//数据库名称及版本号
DataBase supports SQL keywords://SQL关键字
ALPHANUMERIC,AUTOINCREMENT,BINARY,BYTE,CHAR,COLUMN, CONSTRAINT,COUNTER,CURRENCY,DATETIME,DISALLOW,DISTINCTROW, FLOAT4,FLOATS,GENERAL,IEEEDOUBLE,IEEESINGLE,INT,INTEGERI, INTEGERZ,INTEGER4,LEVEL,LOGICAL,LOGICALI,LONG,LONGBINARY, LONGCHAR,LONGTEXT,MEMO,MONEY,NOTE,NUMBER,OLEOBJECT,OPTION, OWNERACCESS,PARAMETERS,PERCENT,PIVOT,REAL,SHORT,SINGLE, SMALLINT,STRING,TABLEID,TEXT,TOP,TRANSFORM,VALUES,VARBINARY,YESNO
DataBase supports ANSI92 Entry Level SQL:YES
DataBase SupportS ANSI92 Full SQL:NO
Database supports Stored Procedure:YES
Database supports Outer Join:YES
C:\MyDemo\mydb>
1.3.5 JDBC数据类型及类型转换
一、JDBC的数据类型
JDBC的sql包中除了与数据库连接有关的抽象接口及与驱动器有关的DriverManager、DriverPropertyInfo等类型外,还定义了若干数据类,用以代表数据库中可能用到的SQL类型。下面我们就对它们逐一进行简略介绍。
1、sql.Date
sql包中的日期类Date是util包中Date类的子类,实际上也是util.Date类的子集。它只处理年月日,而忽略小时和分秒,用以代表SQL的DATE信息。
Date类的构造方法为:
public Date(int year, int mouth, int day)
其中参数格式同util.Date类的构造方法一样,年参数为所需设定的年份减去1900所得的整数值,月参数为0至11,日参数为1至31。如1998年1月23日所对应创建日期类的方法调用为:
Date d=new Date(98,0,23);
Date类还提供两个与String类互相转换的方法,分别是:
public static Date valueOf(String s)
将字符串类参数转换为日期类对象。其中String类参数S的格式为“年-月-日”,加“1997-04-12”。
public String toString()
将日期类对象转换为String类对象表示,同样采用“年-月-日”的格式。
2、sql.Time
该类是util.Date类的子类,也是它的一个子集。在Time类里,只处理小时和分秒,代表SQL的TIME类型。它与sql.Date合起来才表示完整的util.Date类信息。
Time类的构造方法为:
public Time(int hour,int minute,int second)
其中小时参数值为0至23,分秒参数取值均为0至59。
与sql.Date一样,Time类也定义了两个与String类互相转换的函数ValueOf和String。不同的是String类对象的格式为“小时:分:秒”,如“12:26:06”。
3、sql.Timestamp
这个类也是util.Date类的子类,其中除了包含年月日、小时和分秒和信息之外,还加入了纳秒信息(nanosecond),1纳秒即1毫微秒。Timestamp类用来代表SQL时间戳(Timestamp)类型信息。
Timestamp类的构造方法为:
public Timestamp(int year, int mouth, int date, int hour, int minute, int second, int nano)其中纳秒参数的取值从0至999,999,999,其余各参数同前。
Timestamp类特别定义了设置和获得纳秒信息的方法,分别是
public getnanos()
获取时间戳的纳秒部分
public void setNanos(int n)
以给定数值设置时间戳的纳秒部分
4、sql.Types
Types类是Object类的直接子类。在这个类中以静态常量的形式定义了可使用的SQL的数值类型。所有这些类型常量都以前缀
public final static int
的形式标明是公有静态整数,且不可改动。具体的类型名和含义如表1.1所示。其中OTHER用来代表数据库定义的特殊数据,可以用getObject或setObject方法将其映射为一个Java的Object对象。
表1.1 Types中定义的SQL类型
类型名 |
含义 |
BIGINT | 长整型数 |
BINARY | 二进制数 |
BIT | 比特数 |
CHAR | 字符型 |
DATE | 日期型 |
DECIMAL | 十进制数 |
DOUBLE | 双精度数 |
FLOAT | 浮点数 |
INTEGER | 整数 |
LONGVARBINARY | 可变长型二进制数 |
LONGVARCHAR | 可变长型字符 |
NULL | 空类型 |
NUMERIC | 数值型 |
OTHER | 其他类型 |
REAL | 实数 |
SMALLINT | 短整型 |
TIME | 时间类型 |
TIMESTAMP | 时间戳类型 |
TINYINT | 微整型 |
VARBINARY | 可变二进制数 |
VARCHAR | 可变字符型 |
二、SQL与Java
由于SQL数据类型与Java的数据类型不一致,因而在使用Java类型的应用程序与使用SQL类型的数据库之间,需要某种读写类型转换机制。实际上我们前面介绍的ResultSet类的“get”系列方法,Statement及其子类的“set“系列方法和registerOutParameter方法,都是这一转换机制的组成部分。
需要进行的读写转换包括三种情况:
第一种情况是从数据库中读取数值后,存放在ResultSet对象中的是SQL类型的数据。而调用“get”系列方法时,JDBC才将SQL类型转换为指定的Java类型。在一般情形下,SQL类型相对应的Java类型如表1-2所示。
表1.2 SQL类型一般所对应的Java类型
SQL type |
Java type |
CHAR | java.lang.String |
VARCHAR | java.lang.String |
LONGVARCHAR | java.lang.String |
NUMERIC | java.lang.Bignum |
DECIMAL | java.lang.Bignum |
BIT | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
当然,在使用时用户可以指定将SQL类型转换为某个需要的特定类型而不遵循表1.2。例如在结果集中的某个FLOAT型数值,依标准转换应用使用getDouble方法获取,但实际上按用户的不同需求也可以使用getFloat,getInt,甚至gefByte方法获取,但只是有可能影响数值精确度。表1.3列出了对每一SQL类型可用以获取“get”方法的清单,其中“+”表示可以使用该方法,“*”表示最好使用该方法。
表1.3 获取SQL类型使用的“get”方法
\ |
T I N Y I N T |
S M A L L I N T |
I N T E G E R |
B I G I N T |
R E A L |
F L O A T |
D O U B L E |
D E C I M A L |
N U M E R I C |
B I T |
C H A R |
V A R C H A R |
L O N G V A R C H A R |
B I N A R Y |
V A R B I N A R Y |
L O N G V A R B I N A R Y |
D A T E |
T I M E |
T I M E S T A M P |
getByte | * | + | + | + | + | + | + | + | + | + | + | + | + | ||||||
getShort | + | * | + | + | + | + | + | + | + | + | + | + | + | ||||||
getInt | + | + | * | + | + | + | + | + | + | + | + | + | + | ||||||
getLong | + | + | + | * | + | + | + | + | + | + | + | + | + | ||||||
getFloat | + | + | + | + | * | + | + | + | + | + | + | + | + | ||||||
getDouble | + | + | + | + | + | * | * | + | + | + | + | + | + | ||||||
getBignum | + | + | + | + | + | + | + | * | * | + | + | + | + | ||||||
getBoolean | + | + | + | + | + | + | + | + | + | * | + | + | + | ||||||
getString | + | + | + | + | + | + | + | + | + | + | * | * | + | + | + | + | + | + | + |
getBytes | * | * | + | ||||||||||||||||
getDate | + | + | + | * | + | ||||||||||||||
getTime | + | + | + | * | + | ||||||||||||||
getTimestamp | + | + | + | + | * | ||||||||||||||
getAsciiStream | + | + | * | + | + | + | |||||||||||||
getUnicodeStream | + | + | * | + | + | + | |||||||||||||
getBinaryStream | + | + | * | ||||||||||||||||
getObject | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + | + |
注: “+”表示允许使用;“*”表示推荐使用
第二种情形是当用户发出的SQL操作通过PrepareStatement和CallableStatement执行,带有向数据库输入的参数时,需使用这些声明类的“set”系列方法。例如对PrepareStatement类对象pstmt ,调用方法
pstmt.setLong(1,2222222)
驱动器将自动把2222222转换为SQL的BIGINT类型数据,发往数据库。表1-4给出在这一情形下Java数据转换为SQL数据的标准。
表1.4 Java类型所对应转换的SQL类型
Java type |
SQL type |
java.lang.String | VARCHAR or LONGVARCHAR |
java.lang.Bignum | NUMERIC |
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
float | REAL |
double | DOUBLE |
byte[] | VARBINARY or LONGVARBINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
最后一种情形是在使用CallableStatement时,用户可能用到INOUT参数。这时的转换过程最为复杂。首先,使用“set”系列方法给这些参数赋值,驱动器使用表1.4所示的标准映射将Java类型数据换为SQL类型,再发往数据库。其次,使用CallableStatement的registerOutParameter方法,为每一个参数登记其作为返回值时的SQL类型。这一登记类型必须是在sql.Types中定义的名称,如表1.1所示。最后在执行完毕后使用“get”方法取回参数的结果值。
下面的程序片段给出了一次INOUT参数从赋值到取值的完整转换过程,其中cstmt是CallableStatement的实例对象,有两个INOUT参数:
cstmt.setByte(1,25);//参数赋值
cstmt.setLong(2,4678935);
cstmt.registerOutParameter(1,java.sql.Types.TINYINT);
cstmt.registerOutParameter(2,java.sql.Type.BIGINT);//返回类型类型登记
ResultSet rs=cstmt.executeUpdate();
while(rs.next()){...//处理结果}
byte x=cstmt.getByte(1);//取回参数输出值;
long l=cstmt.getLong(2);
我们可以写出这两个INOUT参数的类型转换流程如下所示:
setByte executeUpdate getByte
↓ ↓ ↓
参数1 byte———→TINYINT————→TINYINT———→byte
setLong executeUpdate getLong
↓ ↓ ↓
参数2 long———→BIGINT————→BIGINT———→long
图1.12参数转换流程图
最后要提醒用户注意的是,INOUT参数的“get”方法和“set”方法的类型应当是一致的,如例中参数1的setByte和getByte方法。
本章小结
这一章我们介绍了Java的数据库接口JDBC,说明了建立数据库连接、执行SQL声明以及获取执行结果的完整过程,还列出了Java数据类型与数据库数据类型的对应和转换。通过这一章的学习,读者可以掌握如何使用JDBC和java.sql包括进行各种SQL操作,对网络数据库的用户尤为有用。