第十一讲 JDBC编程
JDBC(Java data Base Connectivity)是Java语言为了支持SQL功能而提供的与数据库相联的用户接口,JDBC中包括了一组由Java语言书写的接口和类,它们都是独立于特定的DBMS,或者说它们可以和各种数据相关联。有了JDBC以后,程序员可以方便地在Java语言中使用SQL语言,从而使Java应用程序或Java applet可以实现对分布在网络上的各种关系数据库的访问。使用了JDBC以后,程序员可以将精力集中于上层的功能实现,而不必关心底层与具体的DBMS的连接和访问过程。
11.1 关系数据库简介
关系数据库系统的思想是由IBM公司的E.F.Codd于1970年率先提出的。
关系数据库支持的数据模型是关系模型。关系模型的基本条件是其关系模式中每个属性值必须是一个不可分割的数据量。简单地说,在关系数据库中数据结构一般是张两维表,这张两维表的每一列的值必须是不可分割的数据项,而任两行应互不相同。
关系模型的主要特点是:
⑴结构简单,表示力强,易于掌握。
⑵语言一体化,非过程化的操作,方便用户使用。
⑶有一定的数学理论作基础,利于进一步研究和发展。
⑷操作耗时,执行效率低。
关系数据库管理系统一般向用户提供数据检索、数据插入、数据删除、数据修改四种基本操作功能。
11.2 JDBC概述
JDBC由一组Java语言编写的类和接口组成,使用内嵌式的SQL,主要实现三方面的功能:建立与数据库的连接,执行SQL声明以及处理SQL执行结果。JDBC支持基本的SQL功能,使用它可方便地与不同的关系型数据库建立连接,进行相关操作,并无需再为不同的DBMS分别编写程序。下面我们先介绍JDBC与SQL及ODBC的关系,再介绍JDBC支持的两种模型,最后介绍JDBC的抽象接口和数据库驱动器Driver。
11.2.1 JDBC与SQL
尽管一般数据库系统在很大范围内支持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的描述信息,再根据所得信息采取特定的查询操作方式。
11.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使用。
11.2.3 JDBC支持的两种模型
在与数据库的连接操作中,JDBC支持两种不同的模型。这两川模型根据用户与数据库的关系层次不同,分别称为两模型和三层模型。
两层模型中,Java的应用程序(Applet或Application)直接与数据库联系。用户的SQL声明被提交给数据库,执行的结果回送给用户,如下图所示。这种模型具有客户机/服务器结构,用户的机器如同客户机,存放数据库的机器则如同服务器,连接两者的可以是局域网,也可以是广域网。
┌─────────┐
│JavaApplication │
│Java Applet │ Clinet Machine
├─────────┤
│JDBC│
└─────────┘
↑
↓
┌─────────┐
│DBMS│ Database Serve
└─────────┘
图11.1 JDBC支持的两层模型
在三层模型中,用户不直接与数据库联系。用户的命令首先发送给一个所谓“中间层”,中间层再将SQL声明发给DMBS。执行的结果也同样由中间层转交,如图11.2所示。三层模型的好处是,可以通过中间层保持对存取权限和公有数据允许修改类型的控制,便于安全管理。同时,用户可以使用一种较为友善的高层API,由中间层转化为恰当的低层命令,保证较好地运行功效。到目前为止,中间层多用C或C++语言编写。随着Java语言的逐步推广,将出现用Java编写的中间层,更好地利用它的健壮性、多线程,安全性等特点。
┌──────────┐
│Java Applet│
│HTML Browse│
└──────────┘
↑
↓
┌──────────┐
│Application Server │
│(Java)│
├──────────┤
│JDBC│
└──────────┘
↑
↓
┌──────────┐
│DBMS│
└──────────┘
图11.2 JDBC支持的三层模型
11.2.4 JDBC的抽象接口
JDBC中最重要的部分是定义了一系列的抽象接口,通过这些接口,JDBC实现了三个基本的功能:建立与数据的连接、执行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声明后产生的数据结果。
11.2.5 JDBC的数据库驱动器Driver
Java的应用程序员通过sql包中定义的一系列抽象类对数据库进行操作,而实现这些抽象类,实际完成操作,则是由数据库驱动器Driver运行的。它们之间的层次关系如图11.3所示
┌─────────┐
│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 |
───────────────────────────────────-┘
图11.3 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)的驱动器效率更高,也更具有通用性。但目前第一、第二类驱动器比较容易获得,使用也较普遍。
16.3 JDBC编程
11.3.1 程序基本结构
一般的JDBC程序都完成三项功能:与数据库建立连接;传送SQL 声明以及对返回结果进行处理。下面我们通过一个具体例子说明这三项功能的实现过程。
例11.1 Creage.java给出了一个简单的JDBC程序,此程序执行后创建一张名为testTable的表,表中包括两个域,域名分别为id和name。
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的注释
//与驱动器建立连接 ,这里可使用不同的特定驱动,如jdbcfororacle或jdbcforsqlserver
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:}
11.3.2 Statement类及其子类
1)Statement接口
Statement stmt=con.createStatement();
//执行SQL声明
int count1=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(1,'wu')");
int count2=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(2,'wang')");
2)PreparedStatement接口
String data[][]={{"5","xu"},{"6","yan"}};
PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable (id,name) VALUES(?,?)");
//参数赋值,执行SQL声明
for (int i=0;i<data.length;i++){
pstmt.setInt(1,Integer.parseInt(data[i][0]));
pstmt.setString(2,data[i][1]);
pstmt.executeUpdate();
}
3)CallableStatement接口
CallableStatement cstmt=con.prepareCall("{call Search(?)}");
//参数赋值
cstmt.setInt(1,934678);
//执行储存过程。
cstmt.execute();
11.3.3 结果集ResultSet及ResultSetMetaData
String query = "SELECT * FROM testTable";
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));
}
11.3.4 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());
//数据库功能信息函数
dma.supportsANSI92EntryLevelSQL()
dma.supportsANSI92FullSQL()
dma.supportsStoredProcedures()
11.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
的形式标明是公有静态整数,且不可改动。具体的类型名和含义如表11.1所示。其中OTHER用来代表数据库定义的特殊数据,可以用getObject或setObject方法将其映射为一个Java的Object对象。
表11.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类型如表11-2所示。
表11.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类型转换为某个需要的特定类型而不遵循表11.2。例如在结果集中的某个FLOAT型数值,依标准转换应用使用getDouble方法获取,但实际上按用户的不同需求也可以使用getFloat,getInt,甚至gefByte方法获取,但只是有可能影响数值精确度。表11.3列出了对每一SQL类型可用以获取“get”方法的清单,其中“+”表示可以使用该方法,“*”表示最好使用该方法。
表11.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数据的标准。
表11.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”系列方法给这些参数赋值,驱动器使用表11.4所示的标准映射将Java类型数据换为SQL类型,再发往数据库。其次,使用CallableStatement的registerOutParameter方法,为每一个参数登记其作为返回值时的SQL类型。这一登记类型必须是在sql.Types中定义的名称,如表11.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参数的类型转换流程如下所示:
setByteexecuteUpdategetByte
↓↓↓
参数1 byte———→TINYINT————→TINYINT———→byte
setLongexecuteUpdategetLong
↓↓↓
参数2 long———→BIGINT————→BIGINT———→long
图11.12参数转换流程图
最后要提醒用户注意的是,INOUT参数的“get”方法和“set”方法的类型应当是一致的,如例中参数1的setByte和getByte方法。