更新時(shí)間:2020-01-15 來(lái)源:黑馬程序員 瀏覽量:
1、什么是JDBC?
在web開(kāi)發(fā)中,不可避免的地要使用數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)和管理數(shù)據(jù)。為了在java語(yǔ)言中提供數(shù)據(jù)庫(kù)訪問(wèn)的支持,Sun公司于1996年提供了一套訪問(wèn)數(shù)據(jù)的標(biāo)準(zhǔn)Java類庫(kù),即JDBC。
JDBC的全稱是Java數(shù)據(jù)庫(kù)連接(Java Database connect),它是一套用于執(zhí)行SQL語(yǔ)句的Java API。應(yīng)用程序可通過(guò)這套API連接到關(guān)系數(shù)據(jù)庫(kù),并使用SQL語(yǔ)句來(lái)完成對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的查詢、更新和刪除等操作。應(yīng)用程序使用JDBC訪問(wèn)數(shù)據(jù)庫(kù)的方式如下圖所示。
從上圖可以看出,應(yīng)用程序使用JDBC訪問(wèn)特定的數(shù)據(jù)庫(kù)時(shí),需要與不同的數(shù)據(jù)庫(kù)驅(qū)動(dòng)進(jìn)行連接。由于不同數(shù)據(jù)庫(kù)廠商提供的數(shù)據(jù)庫(kù)驅(qū)動(dòng)不同,因此,為了使應(yīng)用程序與數(shù)據(jù)庫(kù)真正建立連接,JDBC不僅需要提供訪問(wèn)數(shù)據(jù)庫(kù)的API,還需要封裝與各種數(shù)據(jù)庫(kù)服務(wù)器通信的細(xì)節(jié)為了幫助大家更好地理解應(yīng)用程序如何通過(guò)JDBC訪問(wèn)數(shù)據(jù)庫(kù),下面通過(guò)一張圖來(lái)描述JDBC的具體實(shí)現(xiàn)細(xì)節(jié),如下圖。
從上圖中可以看出,JDBC的實(shí)現(xiàn)包括三部分。
(1)JDBC驅(qū)動(dòng)管理器:負(fù)責(zé)注冊(cè)特定的JDBC驅(qū)動(dòng)器,主要通過(guò)java.sql. Driver Manager類實(shí)現(xiàn)。
(2)JDBC驅(qū)動(dòng)器API:由Sun公司負(fù)責(zé)制定,其中最主要的接口是java.sql. Driver接口。
(3)JDBC驅(qū)動(dòng)器:它是一種數(shù)據(jù)庫(kù)驅(qū)動(dòng),由數(shù)據(jù)庫(kù)廠商創(chuàng)建,也稱為JDBC驅(qū)動(dòng)程序JDBC驅(qū)動(dòng)器實(shí)現(xiàn)了JDBC驅(qū)動(dòng)器API,負(fù)責(zé)與特定的數(shù)據(jù)庫(kù)連接,以及處理通信細(xì)節(jié)。
2、JDBC常用API
在開(kāi)發(fā)JDBC程序前,首先了解一下JDBC常用的API。JDBC API主要位于java.sql包中,該包定義了一系列訪問(wèn)數(shù)據(jù)庫(kù)的接口和類,具體如下。
1. Driver接口
Driver接口是所有JDBC驅(qū)動(dòng)程序必須實(shí)現(xiàn)的接口,該接口專門(mén)提供給數(shù)據(jù)庫(kù)廠商使用。在編寫(xiě)JDBC程序時(shí),必須要把指定數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序或類庫(kù)加載到項(xiàng)目的classpath中。
2. DriverManager類
Driver Manager類用于加載JDBC驅(qū)動(dòng)并且創(chuàng)建與數(shù)據(jù)庫(kù)的連接。在Driver Manager類中,定義了兩個(gè)比較重要的靜態(tài)方法。如表所示:
registerDriver(Driver driver)
該方法用于向 DriverManager中注冊(cè)給定的JDBC驅(qū)動(dòng)程程序
getConnection(String url,String user,String pwd)
該方法用于建立和數(shù)據(jù)庫(kù)的連接,并返回表示連接的 Connection對(duì)象
3、Connection接口
Connection接口代表Java程序和數(shù)據(jù)庫(kù)的連接,在Connection接口中,定義了一系列方法,具體如表所示。
getMetaData()
該方法用于返回表示數(shù)據(jù)庫(kù)的元數(shù)據(jù)的 DatabaseMetaData對(duì)象
createStatement()
用于創(chuàng)建一個(gè)Statement對(duì)象來(lái)將SQL語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù)
prepareStatement(String sql)
用于創(chuàng)建一個(gè)PreparedStatement對(duì)象來(lái)將參數(shù)化的SQL語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù)
prepareCall(String sql)
用于創(chuàng)建一個(gè)CallableStatement對(duì)象來(lái)調(diào)用數(shù)據(jù)庫(kù)存儲(chǔ)過(guò)程
4、Statement接口
Statement接口用于向數(shù)據(jù)庫(kù)發(fā)送SQL語(yǔ)句,在Statement接口中,提供了三個(gè)執(zhí)行SQL語(yǔ)句的方法,具體如表所示。
execute(String sql)
用于執(zhí)行各種SQL語(yǔ)句,該方法返回一個(gè)boolean類型的值,如果為true,表示所執(zhí)行的SQL語(yǔ)句具備查詢結(jié)果,可通過(guò)Statement的getResultSet方法獲得查詢結(jié)果。
executeUpdate(String sql)
用于執(zhí)行SQL中的Insert、update和delete語(yǔ)句。該方法返回一個(gè)int類型的值,表示數(shù)據(jù)庫(kù)中受該SQL語(yǔ)句影響的記錄的數(shù)目。
executeQuery(String sql)
用于執(zhí)行SQL中的select語(yǔ)句,該方法返回一個(gè)表示查詢結(jié)果的ResultSet對(duì)象
5. PreparedStatement接口
PreparedStatement是Statement的子接口,用于執(zhí)行預(yù)編譯的SQL語(yǔ)句。在PreparedStatement接口中,提供了一些基本操作的方法,具體如表下所示。
executeUpdate()
在此PreparedStatement對(duì)象中執(zhí)行SQL語(yǔ)句,該語(yǔ)句必須是個(gè)DML語(yǔ)句或者是無(wú)返回內(nèi)容的SQL語(yǔ)句,比如DDL語(yǔ)句。
executeQuery()
在此PreparedStatement對(duì)象中執(zhí)行SQL查詢,該方法返回的ResultSet對(duì)象
setInt(int parameterIndex, int x)
將指定參數(shù)設(shè)置為給定的int值
setFloat(int parameterIndex, float x)
指定參數(shù)設(shè)置為給定的float值
setString(int parameterIndex, String x)
將指定參數(shù)設(shè)置為給定的String值
setDate(int parameterIndex, Date x)
將指定參數(shù)設(shè)置為給定的Date值
addBatch()
將一組參數(shù)添加到此PreparedStatement對(duì)象的批處理命令中
setCharacterStream(parameterIndex, reader, length)
將指定的輸入流寫(xiě)入數(shù)據(jù)庫(kù)的文本字段
setBinaryStream(parameterIndex, x, length)
將二進(jìn)制的輸入流數(shù)據(jù)寫(xiě)入到二進(jìn)制字段中
需要注意的是,上表中的setDate()方法可以設(shè)置日期內(nèi)容,但參數(shù)Date的類型是java.sq.Date,而不是java.util.Date。
6、CallableStatement接口
CallableStatement是PreparedStatement的子接口,用于執(zhí)行SQL存儲(chǔ)過(guò)程。在Callablestatement按接口中,提供了一些基本操作的方法,具體下表所示:
registerOutParameter(int parameterIndex,int sqlType)
按順序位置將OUT參數(shù)注冊(cè)為SQL類型。其中,parameterIndex表示順序位置,sqlType表示SQL類型
setNull(String parameter Name, int sqlType)
將指定參數(shù)設(shè)置為SQL類型的NULL
setString(String parameterName, String x)
查詢最后一個(gè)讀取的OUT參數(shù)是否為SQL類型的NULL
wasNull()
查詢最后一個(gè)讀取的OUT參數(shù)是否為SQL類型的NULL
getlnt(int parameterIndex)
以Java語(yǔ)言中int值的形式獲取指定的數(shù)據(jù)庫(kù)中INTEGER類型參數(shù)的值
需要注意的是,由于 CallableStatement接口繼承PreparedStatement,PreparedStatement接口又繼承了 Statement,因此CallableStatement接口中除了擁有自己特有的方法,也同時(shí)擁有了這兩個(gè)父接口中的方法。
7、ResultSet接口
ResultSet接口表示 select查詢語(yǔ)句得到的結(jié)果集,該結(jié)果集封裝在一個(gè)邏輯表格中。在 ResultSet接口內(nèi)部有一個(gè)指向表格數(shù)據(jù)行的游標(biāo),ResultSet對(duì)象初始化時(shí),游標(biāo)在表格的第一行之前。下表中列舉了ResultSet接口中的常用方法。
getString(int columnIndex)
用于獲取指定字段的String類型的值,參數(shù)columnIndex代表字段的索引
getString(String columnName)
用于獲取指定字段的String類型的值,參數(shù)column Name代表字段的名稱
getInt(int columnIndex)
用于獲取指定字段的int類型的值,參數(shù)columnIndex代表字段的索引
getInt(String columnName)
用于獲取指定字段的int類型的值,參數(shù)columnName代表字段的名稱
getDate(int columnIndex)
用于獲取指定字段的Date類型的值,參數(shù)columnIndex代表字段的索引
getDate(String columnName)
用于獲取指定字段的Date類型的值,參數(shù)column Name代表字段的名稱
next()
將游標(biāo)從當(dāng)前位置向下移一行
absolute(int row)
將游標(biāo)移動(dòng)到此Resultset對(duì)象的指定行
afterLast()
將游標(biāo)移動(dòng)到此ResultSet對(duì)象的末尾,即最后一行之后
beforeFirst()
將游標(biāo)移動(dòng)到此Resultset對(duì)象的開(kāi)頭,即第一行之前
previous()
將游標(biāo)移動(dòng)到此ResultSet對(duì)象的上一行
last()
將游標(biāo)移動(dòng)到此ResultSet對(duì)象的最
從上表中可以看出,ResultSet接口中定義了大量的getXxx()方法,采用哪種getXxx()方法取決于字段的數(shù)據(jù)類型。程序既可以通過(guò)字段的名稱來(lái)獲取指定數(shù)據(jù),也可以通過(guò)字段的索引來(lái)獲取指定的數(shù)據(jù),字段的索引是從1開(kāi)始編號(hào)的。
3、實(shí)現(xiàn)第一個(gè)JDBC程序
通過(guò)前面的學(xué)習(xí),我們對(duì)JDBC及其常用API有了大致的了解,接下來(lái)就開(kāi)始介紹JDBC編程,JDBC編程大致按照以下幾個(gè)步驟進(jìn)行。
(1) 加載并注冊(cè)數(shù)據(jù)庫(kù)驅(qū)動(dòng),具體方式如下。
DriverManager.registerDriver(Driver driver);
(2) 通過(guò)Driver Manager獲取數(shù)據(jù)庫(kù)連接,具體方式如下。
Connection conn= DriverManager.getConnection(String url, String user, String pass);
從上述方式可以看出,getConnection()方法中有三個(gè)參數(shù),它們分別表示數(shù)據(jù)庫(kù)url、登錄數(shù)據(jù)庫(kù)的用戶名和密碼。數(shù)據(jù)庫(kù)山通常遵循如下形式的寫(xiě)法。
jdbc:subprotocol:subname
上面的URL寫(xiě)法中jdbc部分是固定的,subprotocol指定鏈接達(dá)到特定數(shù)據(jù)庫(kù)的驅(qū)動(dòng)程序,而subname部分則很不固定,也沒(méi)有什么規(guī)律,不同數(shù)據(jù)庫(kù)的形式可能存在較大差異,一Mysql數(shù)據(jù)庫(kù)為例,其形式如下:
jdbc:mysql://hostname:port/databasename
(3)通過(guò)Connection對(duì)象獲取Statement對(duì)象。Connection創(chuàng)建Statement的方式有如下三種。
① createStatement(): 創(chuàng)建基本的Statement對(duì)象
② prepareStatement(): 創(chuàng)建PreparedStatement對(duì)象。
③ preparCall(): 創(chuàng)建CallableStatement對(duì)象。
以創(chuàng)建基本的Statement對(duì)象為例,具體方式如下。
Statement stmt=conn.createStatement();
(4)使用Statement執(zhí)行SQL語(yǔ)句。所有的Statement都有如下三種方法來(lái)執(zhí)行語(yǔ)句。
①execute():可以執(zhí)行任何SQL語(yǔ)句。
②executeQuery():通常執(zhí)行查詢語(yǔ)句,執(zhí)行后返回代表結(jié)果集的Resultset對(duì)象。
③executeUpdate():主要用于執(zhí)行DML和DDL語(yǔ)句。執(zhí)行DML語(yǔ)句,如INSERT、UPDATE或 DELETE時(shí),返回受SQL語(yǔ)句影響的行數(shù),執(zhí)行DDL語(yǔ)句返回0。
以executeQuer()方法為例,具體方式如下。
//執(zhí)行SQL語(yǔ)句,獲取結(jié)果集ResulSet
ResultSet rs=stmt.executQuery(sql);
(5)操作ResultSet結(jié)果集。如果執(zhí)行的SQL語(yǔ)句是查詢語(yǔ)句,執(zhí)行結(jié)果將返回Resultset對(duì)象,該對(duì)象里保存了SQL語(yǔ)句查詢的結(jié)果。程序可以通過(guò)操作該ResultSet對(duì)象來(lái)取出查詢結(jié)果。 ResultSet對(duì)象提供的方法主要可以分為以下兩類。
①next()、previous()、first()、last()、beforeFirst()、afterLast()、absolute()等移動(dòng)記錄指針的方法
②getXxx()獲取指針指向行,特定列的值。
(6)回收數(shù)據(jù)庫(kù)資源。關(guān)閉數(shù)據(jù)庫(kù)連接,釋放資源,包括關(guān)閉ResultSet、Statement和Connection等資源。
至此,JDBC編程的大致步驟已經(jīng)完成,為了幫助讀者快速學(xué)習(xí)如何開(kāi)發(fā)JDBC程序,接下來(lái),編寫(xiě)第一個(gè)JDBC程序,改程序從user表中讀取數(shù)據(jù),并將結(jié)果結(jié)果打印在控制臺(tái),具體步驟入戲所示。
1、搭建實(shí)驗(yàn)環(huán)境
CREATE DATABASE chapter01;
USE chapter01;
CREATE TABLE users(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(40),
password VARCHAR(40),
email VARCHAR(60),
birthday DATE
)CHARACTER SET utf8 COOLLATE utf8_genneral_ci;
數(shù)據(jù)庫(kù)和表創(chuàng)建成功后,再向users表中插入三條數(shù)據(jù),SQL語(yǔ)句如下所示。
INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('zs','123456','zs@sina.com','1980-12-04');
INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('lisi',123456,1isi@sina.com,'1981-12-04');
INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('wangwu',123456,'wangwu@sina.com','1979-12-04');
2、導(dǎo)入數(shù)據(jù)庫(kù)驅(qū)動(dòng)
新建Java工程chapter01,將要訪問(wèn)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)文件添加到classpath中。由于應(yīng)用程序訪問(wèn)的是MySQL數(shù)據(jù)庫(kù),因此,將MySQL的數(shù)據(jù)庫(kù)驅(qū)動(dòng)文件mysql-connector-java-5.0.8-bin.jar添加到classpath中即可。
3、編寫(xiě)JDBC程序
在工程chapter01中,新建Java類Example01,該類用于讀取數(shù)據(jù)庫(kù)中的users表,并將結(jié)果輸出,如例下面案例所示。
package cn.itcast.jdbc.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Date;
public class Example01 {
public static void main(String[] args) throws SQLException {
//1.注冊(cè)數(shù)據(jù)庫(kù)的驅(qū)動(dòng)
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.通過(guò) DriverManager獲取數(shù)據(jù)庫(kù)連接
String url="jdbc:mysql://localhost:3306/chapter01";
String usernames="root";
String password="itcast";
Connection conn=DriverManager.getConnection(url, username, password);
//3.通過(guò) Connection對(duì)象獲取 Statement對(duì)象
Statement stmt= conn.createStatement();
//4.使用 Statement執(zhí)行SQL語(yǔ)句
String sql="select * from users";
ResultSet rs=stmt.executeQuery(sql);
//5、操作 ResultSet結(jié)果集
System.out.println("id|name|password|email|birthday");
while (rs.next()) {
int id=rs.getInt("id"); //通過(guò)列名獲取指定字段的值
String name=rs.getString("name");
String psw=rs.getString("password");
String email=rs.getString("email");
Date birthday=rs.getDate("birthday");
System.out.println(id+"|"+name+"|"+psw+"|"+email+"|"+birthday);
}
//6.回收數(shù)據(jù)庫(kù)
rs.close();
stmt.close();
conn.close();
}
}
程序執(zhí)行后,會(huì)講從users表中讀取到的數(shù)據(jù)打印到控制臺(tái)。
在上面案例中演示了JDBC訪問(wèn)數(shù)據(jù)庫(kù)的步驟。首先注冊(cè)MySQL的數(shù)據(jù)庫(kù)驅(qū)動(dòng)器類,通過(guò) DriverManager獲取一個(gè)Connection對(duì)象,然后使用Connection對(duì)象創(chuàng)建了一個(gè)Statement對(duì)象,Statement對(duì)象能夠通過(guò)executeQuery()方法執(zhí)行SQL語(yǔ)句,并返回結(jié)果集ResultSet對(duì)象。最后,通過(guò)遍歷Resultset對(duì)象便可得到最終的查詢結(jié)果需要注意的是,在實(shí)現(xiàn)第一個(gè)JDBC程序時(shí),還有兩個(gè)方面需要改進(jìn),具體如下。
1、注冊(cè)驅(qū)動(dòng)
在注冊(cè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)時(shí),雖然DriverManager.registerDriver(new com. mysql.jdbc.Driver())方法可以完成,但會(huì)使數(shù)據(jù)庫(kù)驅(qū)動(dòng)被注冊(cè)兩次。這是因?yàn)镈river類的源碼中,已經(jīng)在靜態(tài)代碼塊中完成了數(shù)據(jù)庫(kù)驅(qū)動(dòng)的注冊(cè)。所以,為了避免數(shù)據(jù)庫(kù)驅(qū)動(dòng)被重復(fù)注冊(cè),只需要在程序中加載驅(qū)動(dòng)類即可,具體加載方式如下所示。
Class.forName("com.mysqk.jdbc.Driver");
2、釋放資源
由于數(shù)據(jù)庫(kù)資源非常寶貴,數(shù)據(jù)庫(kù)允許的并發(fā)訪問(wèn)連接數(shù)量有限,因此,當(dāng)數(shù)據(jù)庫(kù)資源使用完畢后,一定要記得釋放資源。為了保證資源的釋放,在Java程序中,應(yīng)該將最終必須要執(zhí)行的操作放在finally代碼塊中,具體方式如下。
if(rs!=null) {
try {
rs.close();
}catch (SQLException e) {
e.printStackTrace();
}
rs=null;
}
if(stmt!=null) {
try {
stmt.close();
}catch (SQLException e) {
e.printStackTrace();
}
stmt=null;
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn=null;
}