|
第十六章 JDBC编程
JDBC(Java data Base
Connectivity)是Java语言为了支持SQL功能而提供的与数据库相联的用户接口,JDBC中包括了一组由Java语言书写的接口和类,它们都是独立于特定的DBMS,或者说它们可以和各种数据相关联。有了JDBC以后,程序员可以方便地在Java语言中使用SQL语言,从而使Java应用程序或Java
applet可以实现对分布在网络上的各种关系数据库的访问。使用了JDBC以后,程序员可以将精力集中于上层的功能实现,而不必关心底层与具体的DBMS的连接和访问过程。
16.1 SQL简介
JDBC最重要的功能是允许用户在Java程序中嵌入SQL语句,以实现对关系数据库的访问。本节中我们将介绍一些有关关系数据库的基本概念,并简单介绍SQL语言。
16.1.1 关系数据库简介
关系数据库系统的思想是由IBM公司的E.F.Codd于1970年率先提出的。
关系数据库支持的数据模型是关系模型。关系模型的基本条件是其关系模式中每个属性值必须是一个不可分割的数据量。简单地说,在关系数据库中数据结构一般是张两维表,这张两维表的每一列的值必须是不可分割的数据项,而任两行应互不相同。
关系模型的主要特点是:
⑴结构简单,表示力强,易于掌握。
⑵语言一体化,非过程化的操作,方便用户使用。
⑶有一定的数学理论作基础,利于进一步研究和发展。
⑷操作耗时,执行效率低。
关系数据库管理系统一般向用户提供数据检索、数据插入、数据删除、数据修改四种基本操作功能。
有关数据库的其它知识,若有需要,读者可以自己查阅相关书籍。
16.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语句、Delect语句和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语句可以实现对数据库的基本删除功能。
例 在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
16.2 JDBC概述
Java语言具有的健壮性、安全性、可移植性、易理解性及自动下载等特点,使它成为一种适用于数据库应用极好的基本语言。在此基础上建立的JDBC,为独立于数据库管理系统DBMS的应用提供了能与多个不同数据库连接的通用接口。
JDBC由一组Java语言编写的类和接口组成,使用内嵌式的SQL,主要实现三方面的功能:建立与数据库的连接,执行SQL声明以及处理SQL执行结果。JDBC支持基本的SQL功能,使用它可方便地与不同的关系型数据库建立连接,进行相关操作,并无需再为不同的DBMS分别编写程序。下面我们先介绍JDBC与SQL及ODBC的关系,再介绍JDBC支持的两种模型,最后介绍JDBC的抽象接口和数据库驱动器Driver。
16.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的描述信息,再根据所得信息采取特定的查询操作方式。
16.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使用。
16.2.3 JDBC支持的两种模型
在与数据库的连接操作中,JDBC支持两种不同的模型。这两川模型根据用户与数据库的关系层次不同,分别称为两模型和三层模型。
两层模型中,Java的应用程序(Applet或Application)直接与数据库联系。用户的SQL声明被提交给数据库,执行的结果回送给用户,如图16.1所示。这种模型具有客户机/服务器结构,用户的机器如同客户机,存放数据库的机器则如同服务器,连接两者的可以是局域网,也可以是广域网。
┌─────────┐ │Java Application│ │ Java Applet │
Clinet
Machine ├─────────┤ │ JDBC │ └─────────┘ ↑ ↓ ┌─────────┐ │ DBMS │
Database Serve └─────────┘ 图16.1 JDBC支持的两层模型
在三层模型中,用户不直接与数据库联系。用户的命令首先发送给一个所谓“中间层”,中间层再将SQL声明发给DMBS。执行的结果也同样由中间层转交,如图16.2所示。三层模型的好处是,可以通过中间层保持对存取权限和公有数据允许修改类型的控制,便于安全管理。同时,用户可以使用一种较为友善的高层API,由中间层转化为恰当的低层命令,保证较好地运行功效。到目前为止,中间层多用C或C++语言编写。随着Java语言的逐步推广,将出现用Java编写的中间层,更好地利用它的健壮性、多线程,安全性等特点。
┌──────────┐ │ Java Applet │ │ HTML
Browse │ └──────────┘ ↑ ↓ ┌──────────┐ │Application
Server
│ │ (Java) │ ├──────────┤ │ JDBC │ └──────────┘ ↑ ↓ ┌──────────┐ │ DBMS │ └──────────┘ 图16.2
JDBC支持的三层模型
16.2.4 JDBC的抽象接口
JDBC中最重要的部分是定义了一系列的抽象接口,通过这些接口,JDBC实现了三个基本的功能:建立与数据的连接、执行SQL声明和处理执行结果。主要的接口和功能实现关系如图16.3所示。
这些接口都存在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声明后产生的数据结果。
在下一节中,我们将对这些接口加以详细的介绍。
16.2.5 JDBC的数据库驱动器Driver
Java的应用程序员通过sql包中定义的一系列抽象类对数据库进行操作,而实现这些抽象类,实际完成操作,则是由数据库驱动器Driver运行的。它们之间的层次关系如图16.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 | └───────────────────────────────────┘
图16.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驱动器完成。
16.3 JDBC编程
本节我们将对JDBC编程进行具体的介绍。本节的程序中使用JDBC驱动器均为JDBC-ODBC
Bridge,使用的数据库为ACCESS数据库。
16.3.1 程序基本结构
一般的JDBC程序都完成三项功能:与数据库建立连接;传送SQL
声明以及对返回结果进行处理。下面我们通过一个具体例子说明这三项功能的实现过程。
例16.1
Creage.java给出了一个简单的JDBC程序,此程序执行后创建一张名为testTable的表,表中包括两个域,域名分别为id和name。
例16.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");再编译运行通过。]
------------------------------
------------------------------
16.3.2 Statement类及其子类
例16.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");//参见例16.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: }
-----
-------
例16.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");//关于此句请参见例16.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:}
----------
------------
例16.4 Callable.java。
import java.net.URL; import java.sql.*; class
Callable{ public static void main(String args[]){ String
url="jdbc:odbc:test";//注意这里不再是demo数据源 try{ //下载jdbc-odbc
bridge
驱动器 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句请参见例16.1的11句注释 //建立连接 Connection
con=DriverManager.getConnection(url,"my-user","my-password"); //创建一个CallableStatement用于执行储存过程 CallableStatement
cstmt=con.prepareCall("{call
Search(?)}"); //参数赋值 cstmt.setInt(1,934678); //执行储存过程。 cstmt.execute(); //处理执行结果 display(cstmt,"Search"); CallableStatement
stmt=con.prepareCall("{call
Delete(?)}"); stmt.setInt(1,934655); stmt.execute(); 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(); } } //处理执行储存过程的结果 private
static void display(CallableStatement cstmt,String name) throws
SQLException{ System.out.println("Excute
procedure"+name); while(true){ //处理情况为:执行结果影响了记录 int
rowCount=cstmt.getUpdateCount(); if(rowCount>0){ System.out.println("Updated
rows
is"+rowCount+"."); //判断是否还有等待处理的结果 if(!cstmt.getMoreResults())break; continue; } //处理情况为:执行结果为ResultSet 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(); } //判断是否还有等待处理的结果 if(!cstmt.getMoreResults())
break; continue; } //处理情况为:执行结果影响了0条记录 if(rowCount==0) System.out.println("No
row is updated!"); if(!cstmt.getMoreResults())
break; } } }
16.3.3 结果集ResultSet
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(); } }
16.3.4 DatabaseMetaData
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(); } }
16.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
的形式标明是公有静态整数,且不可改动。具体的类型名和含义如表16.1所示。其中OTHER用来代表数据库定义的特殊数据,可以用getObject或setObject方法将其映射为一个Java的Object对象。
表16.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类型如表16-2所示。
表16.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类型转换为某个需要的特定类型而不遵循表16.2。例如在结果集中的某个FLOAT型数值,依标准转换应用使用getDouble方法获取,但实际上按用户的不同需求也可以使用getFloat,getInt,甚至gefByte方法获取,但只是有可能影响数值精确度。表16.3列出了对每一SQL类型可用以获取“get”方法的清单,其中“+”表示可以使用该方法,“*”表示最好使用该方法。
表16.3 获取SQL类型使用的“get”方法
|
\ \ 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类型数据,发往数据库。表16-4给出在这一情形下Java数据转换为SQL数据的标准。
表16.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”系列方法给这些参数赋值,驱动器使用表16.4所示的标准映射将Java类型数据换为SQL类型,再发往数据库。其次,使用CallableStatement的registerOutParameter方法,为每一个参数登记其作为返回值时的SQL类型。这一登记类型必须是在sql.Types中定义的名称,如表16.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
图16.12参数转换流程图
最后要提醒用户注意的是,INOUT参数的“get”方法和“set”方法的类型应当是一致的,如例中参数1的setByte和getByte方法。
本章小结
这一章我们介绍了Java的数据库接口JDBC,说明了建立数据库连接、执行SQL声明以及获取执行结果的完整过程,还列出了Java数据类型与数据库数据类型的对应和转换。通过这一章的学习,读者可以掌握如何使用JDBC和java.sql包括进行各种SQL操作,对网络数据库的用户尤为有用。
|