的數(shù)據(jù)庫連接編程(JDBC)技術(shù)
[本講的知識要點]:JDBC、JDBC的工作原理,訪問數(shù)據(jù)庫的方法、Statement、PreparedStatement、CallableStatement,ResultSet等對象的編程使用
9.1 基本知識
9.1.1 JDBC:Java DataBase Connectivity(Java 數(shù)據(jù)庫連接技術(shù)),它是將Java與SQL結(jié)合且獨立于特定的數(shù)據(jù)庫系統(tǒng)的應(yīng)用程序編程接口(API--它是一種可用于執(zhí)行SQL語句的Java API,即由一組用Java語言編寫的類與接口所組成)。
有了JDBC從而可以使Java程序員用Java語言來編寫完整的數(shù)據(jù)庫方面的應(yīng)用程序。另外也可以操作保存在多種不同的數(shù)據(jù)庫管理系統(tǒng)中的數(shù)據(jù),而與數(shù)據(jù)庫管理系統(tǒng)中數(shù)據(jù)存儲格式無關(guān)。同時Java語言的與平臺的無關(guān)性,不必在不同的系統(tǒng)平臺下編寫不同的數(shù)據(jù)庫應(yīng)用程序。
9.1.2 JDBC設(shè)計的目的
(1)ODBC:微軟的ODBC是用C編寫的,而且只適用于Windows平臺,無法實現(xiàn)跨平臺地操作數(shù)據(jù)庫。
(2)SQL語言:SQL盡管包含有數(shù)據(jù)定義、數(shù)據(jù)操作、數(shù)據(jù)管理等功能,但它并不是一個完整的編程語言,而且不支持流控制,需要與其它編程語言相配合使用。
(3)JDBC的設(shè)計:由于Java語言具有健壯性、安全、易使用并自動下載到網(wǎng)絡(luò)等方面的優(yōu)點,因此如果采用Java語言來連接數(shù)據(jù)庫,將能克服ODBC局限于某一系統(tǒng)平臺的缺陷;將SQL語言與Java語言相互結(jié)合起來,可以實現(xiàn)連接不同數(shù)據(jù)庫系統(tǒng),即使用JDBC可以很容易地把SQL語句傳送到任何關(guān)系型數(shù)據(jù)庫中。
(4)JDBC設(shè)計的目的:它是一種規(guī)范,設(shè)計出它的最主要的目的是讓各個數(shù)據(jù)庫開發(fā)商為Java程序員提供標(biāo)準(zhǔn)的數(shù)據(jù)庫訪問類和接口,使得獨立于DBMS的Java應(yīng)用程序的開發(fā)成為可能(數(shù)據(jù)庫改變,驅(qū)動程序跟著改變,但應(yīng)用程序不變)。
9.1.3 JDBC的主要功能:(1)創(chuàng)建與數(shù)據(jù)庫的連接;(2)發(fā)送SQL語句到任何關(guān)系型數(shù)據(jù)庫中;(3)處理數(shù)據(jù)并查詢結(jié)果。
編程實例:
try
{ Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //(1)創(chuàng)建與數(shù)據(jù)庫的連接
Connection con=DriverManager.getConnection("jdbc:odbc:DatabaseDSN","Login","Password");
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from DBTableName");//(2)發(fā)送SQL語句到數(shù)據(jù)庫中
while(rs.next())
{ String name=rs.getString("Name") ; //(3)處理數(shù)據(jù)并查詢結(jié)果。
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
}
rs.close(); //(4)關(guān)閉
stmt.close();
con.close();
}
catch(SQLException e)
{ System.out.println("SQLState:"+ e.getSQLState());
System.out.println("Message:" + e.getMessage());
System.out.println("Vendor:" + e.getErrorCode());
}
9.1.4 JDBC與ODBC的對比,從而體會JDBC的特點
(1)ODBC是用C語言編寫的,不是面向?qū)ο蟮?;而JDBC是用Java編寫的,是面向?qū)ο蟮摹?div style="height:15px;">
(2)ODBC難以學(xué)習(xí),因為它把簡單的功能與高級功能組合在一起,即便是簡單的查詢也會帶有復(fù)雜的任選項;而JDBC的設(shè)計使得簡單的事情用簡單的做法來完成。
(3)ODBC是局限于某一系統(tǒng)平臺的,而JDBC提供Java與平臺無關(guān)的解決方案。
(4)但也可以通過Java來操作ODBC,這可以采用JDBc-ODBC橋接方式來實現(xiàn)(因為Java不能直接使用ODBC,即在Java中使用本地C的代碼將帶來安全缺陷)。
9.1.5 JDBC驅(qū)動程序的類型: 目前比較常見的JDBC驅(qū)動程序可分為以下四個種類:
(1)JDBC-ODBC橋加ODBC驅(qū)動程序
JavaSoft橋產(chǎn)品利用ODBC驅(qū)動程序提供JDBC訪問。注意,必須將ODBC二進制代碼(許多情況下還包括數(shù)據(jù)庫客戶機代碼)加載到使用該驅(qū)動程序的每個客戶機上。因此,這種類型的驅(qū)動程序最適合于企業(yè)網(wǎng)(這種網(wǎng)絡(luò)上客戶機的安裝不是主要問題),或者是用Java編寫的三層結(jié)構(gòu)的應(yīng)用程序服務(wù)器代碼。
JDBC-ODBC 橋接方式利用微軟的開放數(shù)據(jù)庫互連接口(ODBC API)同數(shù)據(jù)庫服務(wù)器通訊,客戶端計算機首先應(yīng)該安裝并配置ODBC driver 和JDBC-ODBC bridge兩種驅(qū)動程序。
(2)本地API
這種類型的驅(qū)動程序把客戶機API上的JDBC調(diào)用轉(zhuǎn)換為Oracle、Sybase、Informix、DB2或其它DBMS的調(diào)用。注意,象橋驅(qū)動程序一樣,這種類型的驅(qū)動程序要求將某些二進制代碼加載到每臺客戶機上。
這種驅(qū)動方式將數(shù)據(jù)庫廠商的特殊協(xié)議轉(zhuǎn)換成Java代碼及二進制類碼,使Java 數(shù)據(jù)庫客戶方與數(shù)據(jù)庫服務(wù)器方通信。例如:Oracle用SQLNet協(xié)議,DB2用IBM 的數(shù)據(jù)庫協(xié)議。數(shù)據(jù)庫廠商的特殊協(xié)議也應(yīng)該被安裝在客戶機上。
(3)JDBC網(wǎng)絡(luò)純Java驅(qū)動程序
這種驅(qū)動程序?qū)DBC轉(zhuǎn)換為與DBMS無關(guān)的網(wǎng)絡(luò)協(xié)議,之后這種協(xié)議又被某個服務(wù)器轉(zhuǎn)換為一種DBMS協(xié)議。這種網(wǎng)絡(luò)服務(wù)器中間件能夠?qū)⑺募僇ava客戶機連接到多種不同的數(shù)據(jù)庫上。所用的具體協(xié)議取決于提供者。通常,這是最為靈活的JDBC驅(qū)動程序。有可能所有這種解決方案的提供者都提供適合于Intranet用的產(chǎn)品。為了使這些產(chǎn)品也支持Internet訪問,它們必須處理Web所提出的安全性、通過防火墻的訪問等方面的額外要求。幾家提供者正將JDBC驅(qū)動程序加到他們現(xiàn)有的數(shù)據(jù)庫中間件產(chǎn)品中。
這種方式是純Java driver。數(shù)據(jù)庫客戶以標(biāo)準(zhǔn)網(wǎng)絡(luò)協(xié)議(如HTTP、SHTTP)同數(shù)據(jù)庫訪問服務(wù)器通信,數(shù)據(jù)庫訪問服務(wù)器然后翻譯標(biāo)準(zhǔn)網(wǎng)絡(luò)協(xié)議成為數(shù)據(jù)庫廠商的專有特殊數(shù)據(jù)庫訪問協(xié)議(也可能用到ODBC driver)與數(shù)據(jù)庫通信。對Internet 和Intranet 用戶而言這是一個理想的解決方案。Java driver 被自動的,以透明的方式隨Applets自Web服務(wù)器而下載并安裝在用戶的計算機上。
(4)本地協(xié)議純Java驅(qū)動程序
這種類型的驅(qū)動程序?qū)DBC調(diào)用直接轉(zhuǎn)換為DBMS所使用的網(wǎng)絡(luò)協(xié)議。這將允許從客戶機機器上直接調(diào)用DBMS服務(wù)器,是Intranet訪問的一個很實用的解決方法。
這種方式也是純Java driver。數(shù)據(jù)庫廠商提供了特殊的JDBC協(xié)議使Java數(shù)據(jù)庫客戶與數(shù)據(jù)庫服務(wù)器通信。然而,將把代理協(xié)議同數(shù)據(jù)庫服務(wù)器通信改用數(shù)據(jù)庫廠商的特殊JDBC driver。這對Intranet 應(yīng)用是高效的,可是數(shù)據(jù)庫廠商的協(xié)議可能不被防火墻支持,缺乏防火墻支持在Internet 應(yīng)用中會存在潛在的安全隱患。
9.2 JDBC的工作原理
JDBC的設(shè)計基于X/Open SQL CLI(調(diào)用級接口)這一模型。它通過定義出一組 API對象和方法以用于同數(shù)據(jù)庫進行交互。
在Java程序中要操作數(shù)據(jù)庫,一般應(yīng)該通過如下幾步(利用JDBC訪問數(shù)據(jù)庫的編程步驟):
(1)加載連接數(shù)據(jù)庫的驅(qū)動程序 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
(2)創(chuàng)建與數(shù)據(jù)源的連接
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url,"Login","Password");
(3)查詢數(shù)據(jù)庫:創(chuàng)建Statement對象并執(zhí)行SQL語句以返回一個ResultSet對象。
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from DBTableName");
(4)獲得當(dāng)前記錄集中的某一記錄的各個字段的值
String name=rs.getString("Name");
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
(5)關(guān)閉查詢語句及與數(shù)據(jù)庫的連接(注意關(guān)閉的順序先rs再stmt最后為con)
rs.close();
stmt.close();
con.close();
9.3 JDBC的結(jié)構(gòu)
JDBC主要包含兩部分:面向Java程序員的JDBC API及面向數(shù)據(jù)庫廠商的JDBC Drive API。
(1)面向Java程序員的JDBC API:Java程序員通過調(diào)用此API從而實現(xiàn)連接數(shù)據(jù)庫、執(zhí)行SQL語句并返回結(jié)果集等編程數(shù)據(jù)庫的能力,它主要是由一系列的接口定義所構(gòu)成。
java.sql.DriveManager:該接口主要定義了用來處理裝載驅(qū)動程序并且為創(chuàng)建新的數(shù)據(jù)庫連接提供支持。
java.sql.Connection:該接口主要定義了實現(xiàn)對某一種指定數(shù)據(jù)庫連接的功能。
java.sql.Statement:該接口主要定義了在一個給定的連接中作為SQL語句執(zhí)行聲明的容器以實現(xiàn)對數(shù)據(jù)庫的操作。它主要包含有如下的兩種子類型。
java.sql.PreparedStatement:該接口主要定義了用于執(zhí)行帶或不帶 IN 參數(shù)的預(yù)編譯 SQL 語句。
java.sql.CallableStatement:該接口主要定義了用于執(zhí)行數(shù)據(jù)庫的存儲過程的雕用。
java.sql.ResultSet:該接口主要定義了用于執(zhí)行對數(shù)據(jù)庫的操作所返回的結(jié)果集。
(2)面向數(shù)據(jù)庫廠商的JDBC Drive API:數(shù)據(jù)庫廠商必須提供相應(yīng)的驅(qū)動程序并實現(xiàn)JDBC API所要求的基本接口(每個數(shù)據(jù)庫系統(tǒng)廠商必須提供對DriveManager、Connection、Statement、ResultSet等接口的具體實現(xiàn)),從而最終保證Java程序員通過JDBC實現(xiàn)對不同的數(shù)據(jù)庫操作。
9.4 數(shù)據(jù)庫應(yīng)用的模型
(1)兩層結(jié)構(gòu)(C/S):在此模型下,客戶端的程序直接與數(shù)據(jù)庫服務(wù)器相連接并發(fā)送SQL語句(但這時就需要在客戶端安裝被訪問的數(shù)據(jù)庫的JDBC驅(qū)動程序),DBMS服務(wù)器向客戶返回相應(yīng)的結(jié)果,客戶程序負(fù)責(zé)對數(shù)據(jù)的格式化。
client端 ODBC/JDBC Server端(DBMS)
或數(shù)據(jù)庫專用協(xié)議
主要的缺點:受數(shù)據(jù)庫廠商的限制,用戶更換數(shù)據(jù)庫時需要改寫客戶程序;受數(shù)據(jù)庫版本的限制,數(shù)據(jù)庫廠商一旦升級數(shù)據(jù)庫,使用該數(shù)據(jù)庫的客戶程序需要重新編譯和發(fā)布;對數(shù)據(jù)庫的操作與處理都是在客戶程序中實現(xiàn),使客戶程序在編程與設(shè)計時較為復(fù)雜。
(2)三(或多)層結(jié)構(gòu)(B/S):在此模型下,主要在客戶端的程序與數(shù)據(jù)庫服務(wù)器之間增加了一個中間服務(wù)器(可以采用C++或Java語言來編程實現(xiàn)),隔離客戶端的程序與數(shù)據(jù)庫服務(wù)器。客戶端的程序(可以簡單為通用的瀏覽器)與中間服務(wù)器進行通信,然后由中間服務(wù)器處理客戶端程序的請求并管理與數(shù)據(jù)庫服務(wù)器的連接。
客戶端程序 HTTP RMI CORBA 中間服務(wù)器 JDBC 數(shù)據(jù)庫服務(wù)器
9.5 通過JDBC 實現(xiàn)對數(shù)據(jù)庫的訪問
(1)引用必要的包
import java.sql.*; //它包含有操作數(shù)據(jù)庫的各個類與接口
(2)加載連接數(shù)據(jù)庫的驅(qū)動程序類
為實現(xiàn)與特定的數(shù)據(jù)庫相連接,JDBC必須加載相應(yīng)的驅(qū)動程序類。這通??梢圆捎肅lass.forName()方法顯式地加載一個驅(qū)動程序類,由驅(qū)動程序負(fù)責(zé)向DriverManager登記注冊并在與數(shù)據(jù)庫相連接時,DriverManager將使用此驅(qū)動程序。
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
注意:這條語句直接加載了sun公司提供的JDBC-ODBC Bridge驅(qū)動程序類。
(3)創(chuàng)建與數(shù)據(jù)源的連接
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url,"Login","Password");
注意:采用DriverManager類中的getConnection()方法實現(xiàn)與url所指定的數(shù)據(jù)源建立連接并返回一個Connection類的對象,以后對這個數(shù)據(jù)源的操作都是基于該Connection類對象;但對于Access等小型數(shù)據(jù)庫,可以不用給出用戶名與密碼。
String url="jdbc:odbc:DatabaseDSN";
Connection con=DriverManager.getConnection(url);
System.out.println(con.getCatalog()); //取得數(shù)據(jù)庫的完整路徑及文件名
JDBC借用了url語法來確定全球的數(shù)據(jù)庫(數(shù)據(jù)庫URL類似于通用的URL),對由url所指定的數(shù)據(jù)源的表示格式為
jdbc::[ database locator]
jdbc---指出要使用JDBC
subprotocal---定義驅(qū)動程序類型
database locator---提供網(wǎng)絡(luò)數(shù)據(jù)庫的位置和端口號(包括主機名、端口和數(shù)據(jù)庫系統(tǒng)名等) jdbc:odbc://host.domain.com:port/databasefile
主協(xié)議jdbc 驅(qū)動程序類型為odbc,它指明JDBC管理器如何訪問數(shù)據(jù)庫,該例指名為采用JDBC-ODBC橋接方式;其它為數(shù)據(jù)庫的位置表示。
例如:裝載mySQL JDBC驅(qū)動程序
Class.forName("org.gjt.mm.mysql.Driver ");
String url
="jdbc:mysql://localhost/softforum?user=soft&password=soft1234&useUnicode=true&characterEncoding=8859_1"
//testDB為你的數(shù)據(jù)庫名
Connection conn= DriverManager.getConnection(url);
例如:裝載Oracle JDBC OCI驅(qū)動程序(用thin模式)
Class.forName("oracle.jdbc.driver.OracleDriver ");
String url="jdbc:oracle:thin:@localhost:1521:orcl";
//orcl為你的數(shù)據(jù)庫的SID
String user="scott";
String password="tiger";
Connection conn= DriverManager.getConnection(url,user,password);
注意:也可以通過con.setCatalog("MyDatabase")來加載數(shù)據(jù)庫。
例如:裝載DB2驅(qū)動程序
Class.forName("com.ibm.db2.jdbc.app.DB2Driver ")
String url="jdbc:db2://localhost:5000/sample";
//sample為你的數(shù)據(jù)庫名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
例如:裝載MicroSoft SQLServer驅(qū)動程序
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver ");
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=pubs";
//pubs為你的數(shù)據(jù)庫的
String user="sa";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
(4)查詢數(shù)據(jù)庫的一些結(jié)構(gòu)信息
這主要是獲得數(shù)據(jù)庫中的各個表,各個列及數(shù)據(jù)類型和存儲過程等各方面的信息。根據(jù)這些信息,從而可以訪問一個未知結(jié)構(gòu)的數(shù)據(jù)庫。這主要是通過DatabaseMetaData類的對象來實現(xiàn)并調(diào)用其中的方法來獲得數(shù)據(jù)庫的詳細信息(即數(shù)據(jù)庫的基本信息,數(shù)據(jù)庫中的各個表的情況,表中的各個列的信息及索引方面的信息)。
DatabaseMetaData dbms=con.getMetaData();
System.out.println("數(shù)據(jù)庫的驅(qū)動程序為 "+dbms.getDriverName());
(5)查詢數(shù)據(jù)庫中的數(shù)據(jù):
在JDBC中查詢數(shù)據(jù)庫中的數(shù)據(jù)的執(zhí)行方法可以分為三種類型,分別對應(yīng)Statement (用于執(zhí)行不帶參數(shù)的簡單SQL語句字符串),PreparedStatement(預(yù)編譯SQL語句)和CallableStatement(主要用于執(zhí)行存儲過程)三個接口。
9.5.1、實現(xiàn)對數(shù)據(jù)庫的一般查詢Statement
1、創(chuàng)建Statement對象(要想執(zhí)行一個SQL查詢語句,必須首先創(chuàng)建出Statement對象,它封裝代表要執(zhí)行的SQL語句)并執(zhí)行SQL語句以返回一個ResultSet對象,這可以通過Connection類中的createStatement()方法來實現(xiàn)。
Statement stmt=con.createStatement();
2、執(zhí)行一個SQL查詢語句,以查詢數(shù)據(jù)庫中的數(shù)據(jù)。Statement接口提供了三種執(zhí)行SQL語句的方法:executeQuery()、executeUpdate() 和execute()。具體使用哪一個方法由SQL語句本身來決定。
l 方法 executeQuery 用于產(chǎn)生單個結(jié)果集的語句,例如 SELECT 語句等。
l 方法 executeUpdate 用于執(zhí)行INSERT、UPDATE或DELETE 語句以及SQL DDL(數(shù)據(jù)定義語言)語句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或DELETE 語句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一個整數(shù),指示受影響的行數(shù)(即更新計數(shù))。對于 CREATE TABLE 或DROP TABLE 等不操作行的語句,executeUpdate 的返回值總為零。
l 方法 execute 用于執(zhí)行返回多個結(jié)果集、多個更新計數(shù)或二者組合的語句。一般不會需要該高級功能。
下面給出通過Statement類中的executeQuery()方法來實現(xiàn)的代碼段。executeQuery()方法的輸入?yún)?shù)是一個標(biāo)準(zhǔn)的SQL查詢語句,其返回值是一個ResultSet類的對象。
ResultSet rs=stmt. executeQuery ("select * from DBTableName");
要點:①JDBC在編譯時并不對將要執(zhí)行的SQL查詢語句作任何檢查,只是將其作為一個String類對象,直到驅(qū)動程序執(zhí)行SQL查詢語句時才知道其是否正確。對于錯誤的SQL查詢語句,在執(zhí)行時將會產(chǎn)生 SQLException。
②一個Statement對象在同一時間只能打開一個結(jié)果集,對第二個結(jié)果集的打開隱含著對第一個結(jié)果集的關(guān)閉。
③如果想對多個結(jié)果集同時操作,必須創(chuàng)建出多個Statement對象,在每個Statement對象上執(zhí)行SQL查詢語句以獲得相應(yīng)的結(jié)果集。
④如果不需要同時處理多個結(jié)果集,則可以在一個Statement對象上順序執(zhí)行多個SQL查詢語句,對獲得的結(jié)果集進行順序操作。
import java.sql.*;
public class ResultSetTest
{ public static void main(String args[])
{ try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection("jdbc:odbc:studlist");
Statement stmt=con.createStatement();
ResultSet rs1=stmt.executeQuery("select name from student");
ResultSet rs2=stmt.executeQuery("select age from student");
//此時rs1已經(jīng)被關(guān)閉
while(rs2.next())
{
System.out.println(rs2.getObject(1));
}
rs2.close();
stmt.close();
con.close();
}
catch(Exception e)
{
System.out.println(e);
}
}
}
注意:
此時顯示出的將是姓名還是年齡?(將顯示的是rs2的結(jié)果集的內(nèi)容,即學(xué)生的年齡,因為采用JDBC-ODBC方式的驅(qū)動程序時,并且是采用同一個Statement對象,它只會保留最新的結(jié)果集,rs1中的內(nèi)容將會被新的結(jié)果集所取代)。
3、 關(guān)閉Statement對象:每一個Statement對象在使用完畢后,都應(yīng)該關(guān)閉。
stmt.close();
9.5.2、預(yù)編譯方式執(zhí)行SQL語句PreparedStatement
由于Statement對象在每次執(zhí)行SQL語句時都將該語句傳給數(shù)據(jù)庫,如果需要多次執(zhí)行同一條SQL語句時,這樣將導(dǎo)致執(zhí)行效率特別低,此時可以采用PreparedStatement對象來封裝SQL語句。如果數(shù)據(jù)庫支持預(yù)編譯,它可以將SQL語句傳給數(shù)據(jù)庫作預(yù)編譯,以后每次執(zhí)行該SQL語句時,可以提高訪問速度;但如果數(shù)據(jù)庫不支持預(yù)編譯,將在語句執(zhí)行時才傳給數(shù)據(jù)庫,其效果類同于Statement對象。
另外PreparedStatement對象的SQL語句還可以接收參數(shù),可以用不同的輸入?yún)?shù)來多次執(zhí)行編譯過的語句,較Statement靈活方便(詳見后文介紹)。
1、 創(chuàng)建PreparedStatement對象:從一個Connection對象上可以創(chuàng)建一個PreparedStatement對象,在創(chuàng)建時可以給出預(yù)編譯的SQL語句。
PreparedStatement pstmt=con.prepareStatement("select * from DBTableName");
2、 執(zhí)行SQL語句:可以調(diào)用executeQuery()來實現(xiàn),但與Statement方式不同的是,它沒有參數(shù),因為在創(chuàng)建PreparedStatement對象時已經(jīng)給出了要執(zhí)行的SQL語句,系統(tǒng)并進行了預(yù)編譯。
ResultSet rs=pstmt.executeQuery(); // 該條語句可以被多次執(zhí)行
3、關(guān)閉PreparedStatement
pstmt.close(); //其實是調(diào)用了父類Statement類中的close()方法
9.5.3、執(zhí)行存儲過程CallableStatement
CallableStatement類是PreparedStatement類的子類,因此可以使用在PreparedStatement類及Statement類中的方法,主要用于執(zhí)行存儲過程。
1、 創(chuàng)建CallableStatement對象:使用Connection類中的prepareCall方法可以創(chuàng)建一個CallableStatement對象,其參數(shù)是一個String對象,一般格式為:
l 不帶輸入?yún)?shù)的存儲過程“{call 存儲過程名()}”。
l 帶輸入?yún)?shù)的存儲過程“{call存儲過程名(?, ?)}”
l 帶輸入?yún)?shù)并有返回結(jié)果參數(shù)的存儲過程“{? = call 存儲過程名(?, ?, ...)}”
CallableStatement cstmt=con.prepareCall("{call Query1()}");
2、 執(zhí)行存儲過程:可以調(diào)用executeQuery()方法來實現(xiàn)。
ResultSet rs=cstmt.executeQuery();
3、關(guān)閉CallableStatement
cstmt.close(); //其實是調(diào)用了父類Statement類中的close()方法
(6)檢索記錄集以獲得當(dāng)前記錄集中的某一記錄的各個字段的值
9.5.4、ResultSet對象:
① 執(zhí)行完畢SQL語句后,將返回一個ResultSet類的對象,它包含所有的查詢結(jié)果。但對ResultSet類的對象方式依賴于光標(biāo)(Cursor)的類型,而對每一行中的各個列,可以按任何順序進行處理(當(dāng)然,如果按從左到右的順序?qū)Ω髁羞M行處理可以獲得較高的執(zhí)行效率);
ResultSet類中的Course方式主要有:
ResultSet.TYPE_FORWARD_ONLY(為缺省設(shè)置):光標(biāo)只能前進不能后退,也就是只能從第一個一直移動到最后一個。
ResultSet.TYPE_SCROLL_SENSITIVE:允許光標(biāo)前進或后退并感應(yīng)到其它ResultSet的光標(biāo)的移動情形。
ResultSet.TYPE_SCROLL_INSENSITIVE:允許光標(biāo)前進或后退并不能感應(yīng)到其它ResultSet的光標(biāo)的移動情形。
ResultSet類中的數(shù)據(jù)是否允許修改主要有:
ResultSet.CONCUR_READ_ONLY(為缺省設(shè)置):表示數(shù)據(jù)只能只讀,不能更改。
ResultSet.CONCUR_UPDATABLE:表示數(shù)據(jù)允許被修改。
可以在創(chuàng)建Statement或PreparedStatement對象時指定ResultSet的這兩個特性。
Statement stmt=con.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
或
PreparedStatement pstmt=con.PrepareStatement("insert into bookTable values (?,?,?)",ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
② ResultSet類的對象維持一個指向當(dāng)前行的指針,利用ResultSet類的next()方法可以移動到下一行(在JDBC中,Java程序一次只能看到一行數(shù)據(jù)),如果next()的返回值為false,則說明已到記錄集的尾部。另外JDBC也沒有類似ODBC 的書簽功能的方法。
③ 利用ResultSet類的getXXX()方法可以獲得某一列的結(jié)果,其中XXX代表JDBC中的Java數(shù)據(jù)類型,如 getInt()、getString()、getDate()等。訪問時需要指定要檢索的列(可以采用 int值作為列號(從1開始計數(shù))或指定列(字段)名方式,但字段名不區(qū)別字母的大小寫)。
while(rs.next())
{ String name=rs.getString("Name"); //采用“列名”的方式訪問數(shù)據(jù)
int age=rs.getInt("age");
float wage=rs.getFloat("wage");
String homeAddress=rs.getString(4); //采用“列號”的方式訪問數(shù)據(jù)
}
9.5.5、數(shù)據(jù)轉(zhuǎn)換
利用ResultSet類的getXXX()方法可以實現(xiàn)將ResultSet中的SQL數(shù)據(jù)類型轉(zhuǎn)換為它所返回的Java數(shù)據(jù)類型。
9.5.6、NULL結(jié)果值
要確定給定結(jié)果值是否是JDBC NULL,必須先讀取該列,然后使用ResultSet.wasNull
方法檢查該次讀取是否返回JDBC NULL。
當(dāng)使用ResultSet.getXXX方法讀取JDBC NULL時,方法wasNull將返回下列值之一:
(1)Javanull值
對于返回Java對象的getXXX方法(例如getString、getBigDecimal、getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、getObject等)。
(2)零值:對于getByte、getShort、getInt、getLong、getFloat和getDouble。
(3)false值:對于getBoolean
9.5.6、獲得結(jié)果集中的結(jié)構(gòu)信息:利用ResultSet類的getMetaData()方法來獲得結(jié)果集中的一些結(jié)構(gòu)信息(主要提供用來描述列的數(shù)量、列的名稱、列的數(shù)據(jù)類型。利用ResulSetMetaData類中的方法)。
ResultsetMetaData rsmd=rs.getMetaData();
rsmd.getColumnCount(); //返回結(jié)果集中的列數(shù)
rsmd.getColumnLabel(1); //返回第一列的列名(字段名)
例如:
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from TableName");
for(int i=1; i<=rs.getMetaData().getColumnCount(); i++) //跟蹤顯示各個列的名稱
{ System.out.print(rs. getColumnName (i)+"\t");
}
while(rs.next())
{ //跟蹤顯示各個列的值
for(int j=1; j<=rs.getMetaData().getColumnCount(); j++)
{ System.out.print(rs.getObject(j)+"\t");
}
}
9.6、更新數(shù)據(jù)庫
前面主要介紹如何實現(xiàn)對數(shù)據(jù)庫的查詢操作,但在許多應(yīng)用中需要實現(xiàn)對數(shù)據(jù)庫的更新,這主要涉及修改、插入和刪除等(即SQL語句中的Insert、Update、Delete、Creat、Drap等)。仍然通過創(chuàng)建Statement對象來實現(xiàn),但不再調(diào)用executeQuery()方法,而是使用executeUpdate()方法。
要點F:正確區(qū)分Statement類中的executeQuery()、execute()和executeUpdate()方法的用法:(1)
executeQuery() 執(zhí)行一般的SQL查詢語句(即SELECT語句)并返回Resultset對象;(2)execute()可以執(zhí)行各種SQL查詢語句,并可能返回多個結(jié)果集(這一般主要發(fā)生在執(zhí)行了返回多個結(jié)果集的存儲過程時),此時可以采用Resultset類的getResultSet()來獲得當(dāng)前的結(jié)果集;(3)executeUpdate()執(zhí)行對數(shù)據(jù)庫的更新的SQL語句或DDL語句。
9.6.1 對表中的記錄進行操作
對一個表中的記錄可以進行修改、插入和刪除等操作,分別對應(yīng)SQL的Update、 Insert、Delete操作;executeUpdate()方法的輸入?yún)?shù)仍然為一個String對象(即所要執(zhí)行的SQL語句),但輸出參數(shù)不是ResultSet對象,而是一個整數(shù)(它代表操作所影響的記錄行數(shù))。
Statement stmt=con.createStatement();
stmt.executeUpdate("Update bookTable set Title='Java2' where Author='zhang'");
stmt.executeUpdate("Delete from bookTable where Author='zhang'");
stmt.executeUpdate("Insert into bookTable(BookID,Author,Title) values(1,'Li Ming','Java2')"); //未給出的列,其值為NULL
程序?qū)嵗簩?shù)據(jù)庫中的表進行更新操作并顯示操作前后的結(jié)果
import java.sql.*;
public class DBUpdateSetTest
{ public static void main(String args[])
{ try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection("jdbc:odbc:studlist");
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from student");
System.out.println("Result before executeUpdate");
while(rs.next())
{
System.out.println(rs.getString("name"));
System.out.println(rs.getString("age"));
}
stmt.executeUpdate("Update student set name='Yang' where id=0");
stmt.executeUpdate("Delete from student where id=2");
stmt.executeUpdate("Insert into student(id,name,age,sex) values(2,'zhang',30,true)");
rs=stmt.executeQuery("select * from student");
System.out.println("Result After executeUpdate");
while(rs.next())
{
System.out.println(rs.getString("name"));
System.out.println(rs.getString("age"));
}
rs.close();
stmt.close();
con.close();
}
catch(Exception e)
{
System.out.println(e);
}
}
}
9.6.2 創(chuàng)建和刪除表
創(chuàng)建和刪除一個表主要對應(yīng)于SQL的Create Table和Drop Table語句。這可以通過Statement對象的executeUpdate()方法來完成。
① 創(chuàng)建表
Statement stmt=con.createStatement();
stmt.executeUpdate("create table TableName(ID integer, Name VARCHAR(20), Age integer)");
stmt.executeUpdate("Insert into TableName(ID, Name, Age) values(1,'Yang Ming',30)");
② 刪除表
Statement stmt=con.createStatement();
stmt.executeUpdate("Drop Table TableName");
9.6.3 增加和刪除表中的列
對一個表的列進行更新操作主要是使用SQL的ALTER Table語句。對列所進行的更新操作會影響到表中的所有的行。
① 增加表中的一列
Statement stmt=con.createStatement();
stmt.executeUpdate("Alter Table TableName add Column Address VarChar(50)");
stmt.executeUpdate("Update TableName set Address='Beijing,China' where ID=1");
② 刪除表中的一列
Statement stmt=con.createStatement();
stmt.executeUpdate("Alter Table TableName Drop Column Address");
stmt.executeQuery("Select * from TableName");
9.6.4 利用PreparedStatement對象實現(xiàn)數(shù)據(jù)更新
同SQL查詢語句一樣,對數(shù)據(jù)更新語句時也可以在PreparedStatement對象上執(zhí)行。使用PreparedStatement對象,只需傳遞一次SQL語句,可以多次執(zhí)行它,并且可以利用數(shù)據(jù)庫的預(yù)編譯技術(shù),提高執(zhí)行效率。另外也可以接受參數(shù)。
PreparedStatement pstmt=con.prepareStatement("Update TableName set Address='Beijing,China' where ID >1");
pstmt.executeUpdate();
9.7 參數(shù)的輸入與輸出
要實現(xiàn)使用SQL語句的輸入與輸出參數(shù),必須在PreparedStatement類的對象上進行操作;同時由于CallableStatement類是PrepareStatement類的子類,所以在CallableStatemen對象上的操作也可以使用輸入與輸出參數(shù);其主要的編程原理是在生成CallableStatement或PreparedStatement類的對象時,可以在SQL語句中指定輸入或輸出參數(shù),在執(zhí)行這個SQL語句之前,要對輸入?yún)?shù)進行賦值。
(1)使用PreparedStatement類的對象
通過prepareStatement類的對象可以實現(xiàn)在查詢語句與數(shù)據(jù)更新語句方面都可以設(shè)置輸入?yún)?shù)。
具體的方法是在SQL語句中用“?”標(biāo)明參數(shù),在執(zhí)行SQL語句之前,使用setXXX方法給參數(shù)賦值,然后使用executeQuery()或executeUpdate()來執(zhí)行這個SQL語句。每次執(zhí)行SQL語句之前,可以給參數(shù)重新賦值。
setXXX方法用于給相應(yīng)的輸入?yún)?shù)進行賦值,其中XXX是JDBC的數(shù)據(jù)類型,如:Int、String等。setXXX方法有兩個參數(shù),第一個是要賦值的參數(shù)在SQL語句中的位置, SQL語句中的第一個參數(shù)的位置為1,第二個參數(shù)的位置為2;setXXX方法的第二個參數(shù)是要傳遞的值,如100、“Peking”等,隨XXX的不同而為不同的類型。
PreparedStatement pstmt=con.prepareStatement("Update TableName set Name=? where ID=?");
pstmt.setString(1,"zhang Hua"); //設(shè)置第一個參數(shù)(Name)為 “zhang Hua”
for(int i=1;i<3;i++)
{ pstmt.setInt(2,i); //設(shè)置第二個參數(shù)(ID)為 1,2
pstmt.executeUpdate();
}
要點:最終實現(xiàn) Update TableName set Name=zhang Hua where ID=1 與Update TableName set Name=zhang Hua where ID=2的效果。
(2)使用CallableStatement對象
如果要求調(diào)用數(shù)據(jù)庫的存儲過程,要使用CallableStatement對象。另外還有些存儲過程要求用戶輸入?yún)?shù),這可以在生成CallableStatement對象的存儲過程調(diào)用語句中設(shè)置輸入?yún)?shù)。在執(zhí)行這個存儲過程之前使用setXXX方法給參數(shù)賦值,然后再執(zhí)行這個存儲過程。
CallableStatement cstmt=con.prepareCall("{call Query(?)}"); //Query為存儲過程名
cstmt.setString(1,"輸入?yún)?shù)"); //為存儲過程提供輸入?yún)?shù)
ResultSet rs=cstmt.executeQuery();
(3)接收輸出參數(shù)
某些存儲過程可能會返回輸出參數(shù),這時在執(zhí)行這個存儲過程之前,必須使用CallableStatement的registerOutParameter方法首先登記輸出參數(shù),在registerOutParameter方法中要給出輸出參數(shù)的相應(yīng)位置以及輸出參數(shù)的SQL數(shù)據(jù)類型。在執(zhí)行完存儲過程以后,必須使用getXXX方法來獲得輸出參數(shù)的值。并在getXXX方法中要指出獲得哪一個輸出參數(shù)(通過序號來指定)的值。
實例:存儲過程getTestData有三個輸入?yún)?shù)并返回一個輸出參數(shù),類型分別為VARCHAR。在執(zhí)行完畢后,分別使用getString()方法來獲得相應(yīng)的值。
CallableStatement cstmt = con.prepareCall(“{? = call getTestData (?,?,?)}”);
cstmt.setString(1,Value); //設(shè)置輸入?yún)?shù)
cstmt.setInt(2,Value);
cstmt.setFloat(3,Value);
cstmt.registerOutParameter(1,java.sql.Types.VARCHAR); //登記輸出參數(shù)
ResultSet rs = cstmt.executeQuery(); //執(zhí)行存儲過程
rs.getString(1); //獲得第一個字段的值
String returnResult=cstmt.getString(1); //獲得返回的輸出參數(shù)的值
要點:由于getXXX方法不對數(shù)據(jù)類型作任何轉(zhuǎn)換,在registerOutParameter方法中指明數(shù)據(jù)庫將返回的SQL數(shù)據(jù)類型,在執(zhí)行完存儲過程以后必須采用相應(yīng)匹配的getXXX方法來獲得輸出參數(shù)的值。
9.8 批量處理JDBC語句提高處理速度
有時候JDBC運行得不夠快,這可以使用數(shù)據(jù)庫相關(guān)的存儲過程。當(dāng)然,作為存儲過程的一個替代方案,可以試試使用Statement 的批量處理特性以提高速度。
存儲過程的最簡單的形式就是包含一系列SQL語句的過程,將這些語句放在一起便于在同一個地方管理也可以提高速度。Statement 類可以包含一系列SQL語句,因此允許在同一個數(shù)據(jù)庫事務(wù)執(zhí)行所有的那些語句而不是執(zhí)行對數(shù)據(jù)庫的一系列調(diào)用。
使用批量處理功能涉及下面的兩個方法:
addBatch(String) 方法
executeBatch方法
如果你正在使用Statement 那么addBatch 方法可以接受一個通常的SQL語句,或者如果你在使用PreparedStatement ,那么也可以什么都不向它增加。
executeBatch 方法執(zhí)行那些SQL語句并返回一個int值的數(shù)組,這個數(shù)組包含每個語句影響的數(shù)據(jù)的行數(shù)。
注意:如果將一個SELECT語句或者其他返回一個ResultSet的SQL語句放入批量處理中就會導(dǎo)致一個SQLException異常。
關(guān)于java.sql.Statement 的簡單范例可以是:
con = DriverManager.getConnection(url,"myLogin", "myPassword");
con.setAutoCommit(false);
stmt = con.createStatement();
stmt.addBatch("INSERT INTO student " + "VALUES(4,'Yang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(5,'li',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(6,'zhang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(7,'wang',20,True)");
stmt.addBatch("INSERT INTO student " + "VALUES(8,'liu',20,True)");
int [] updateCounts = stmt.executeBatch();
con.commit();
con.setAutoCommit(true);
PreparedStatement 有些不同,它只能處理一部分SQL語法,但是可以有很多參數(shù),因此重寫上面的范例的一部分就可以得到下面的結(jié)果:
// 注意這里沒有刪除語句
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO student VALUES(?,?,?,?)"
);
User[ ] users = ...;
for(int i=0; i
stmt.setInt(1, users[i].getID());
stmt.setString(2, users[i].getName());
stmt.setInt(3, users[i].getAge());
stmt.setBoolean(4, users[i].getSex());
stmt.addBatch( );
}
int[ ] counts = stmt.executeBatch();
如果你不知道你的語句要運行多少次,那么這是一個很好的處理SQL代碼的方法。在不使用批量處理的情況下,如果添加50個用戶,那么性能就有影響,如果某個人寫了一個腳本添加一萬個用戶,程序可能變得很糟糕。添加批處理功能就可以幫助提高性能,而且在后面的那種情況下代碼的可讀性也會更好。