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