在
Java SE 6
所提供的諸多新特性和改進(jìn)中,值得一提的是為
Java
程序提供數(shù)據(jù)庫訪問機(jī)制的
JDBC
版本升級到了
4.0,
這個(gè)以
JSR-221
為代號的版本
,
提供了更加便利的代碼編寫機(jī)制及柔性
,
并且支持更多的數(shù)據(jù)類型
.
在本文中,我們將從編碼的易用性及柔性的角度探討
JDBC 4.0
所帶來的新特性及改進(jìn)。
JDBC 4.0
的新特性
JDBC 4.0
文檔列舉了
20
個(gè)改進(jìn)及新特性
,
大小不等
.
本文無法做到盡述其詳
,
為此筆者根據(jù)其功能特點(diǎn)及應(yīng)用領(lǐng)域?qū)⑵浞譃橄率鏊念悾?/span>
1.
驅(qū)動(dòng)及連接管理
2.
異常處理
3.
數(shù)據(jù)類型支持
4.
API
的變化
下面按照上述四類展開詳述:
驅(qū)動(dòng)及連接管理
驅(qū)動(dòng)及連接的使用和結(jié)果集管理
—
在
JDBC
的很多地方都發(fā)生了顯著的變化
連接數(shù)據(jù)庫變得更加容易
如果您曾經(jīng)有過
JDBC
開發(fā)的經(jīng)驗(yàn)
,
那么我確信您還保存著一份建立數(shù)據(jù)庫連接所必須的工作列表
.
而列表中的第一項(xiàng)即加載一個(gè)合適的驅(qū)動(dòng)程序
.
您是否想過這個(gè)步驟應(yīng)該被改進(jìn)呢
?
在此版
JDBC
中做到了
.
您不必再顯式地加載
Class.forName
了
.
當(dāng)您的程序首次試圖連接數(shù)據(jù)庫時(shí)
,
DriverManager
自動(dòng)加載驅(qū)動(dòng)到當(dāng)前應(yīng)用的
CLASSPATH
.
這是
JDBC
的一個(gè)比較大的改動(dòng)
.
盡管
DriverManager
現(xiàn)在可以自動(dòng)地加載驅(qū)動(dòng)
,
建立一個(gè)
DataSource
對象
仍是獲取連接的推薦的方法
.
因?yàn)榭梢栽谂渲弥袑?shù)據(jù)源指向不同的數(shù)據(jù)庫,
DataSource
更具透明性和靈活性。
這樣就可以訪問另一個(gè)數(shù)據(jù)庫實(shí)例不需更改現(xiàn)有的任意一行代碼
,
甚至數(shù)據(jù)庫的驅(qū)動(dòng)完全不同也沒有關(guān)系
.
ResultSet
的使用變得更為靈活
ResultSet
接口的層次結(jié)構(gòu)當(dāng)前為編程的靈活性提供了一些新的機(jī)制
.
RowSet
子接口滾動(dòng)、可提交并可離線編輯的
ResultSet
.
WebRowSet
子接口提供了從數(shù)據(jù)庫表中獲取數(shù)據(jù)
,
并將其序列化為
XML
文檔
,
抑或是將
XML
解析成
result set
的能力
.
盡管上個(gè)版本的
JDBC
也提供了
RowSet
接口層次
,當(dāng)前的版本對于
SQLXML
數(shù)據(jù)類型支持得更好
(
稍后討論
)
,這些特性是的
JDBC
編程更加容易且具靈活性
.
提供了更多的
API
本版
JDBC
提供了更多的
API
以實(shí)現(xiàn)訪問
SQL:2003
具備的新特性
.
此外
,
為了更好的操縱修改數(shù)據(jù),
JDBC
也增添了許多的方法。
.
現(xiàn)在我們來看一些代碼并討論下面
Example1
類的輸出結(jié)果
.
它將連接嵌入式的
Apache Derby
數(shù)據(jù)庫并在控制臺上顯示輸出結(jié)果
.
盡管
JDBC 4.0
已推出幾個(gè)月了
,
筆者發(fā)現(xiàn)只有
Apache Derby
提供了支持
JDBC 4.0
規(guī)范的驅(qū)動(dòng)
(
截至
2007
年
3
月
).
本文的所有例子均用
JDK 1.6
和
Apache Derby
數(shù)據(jù)庫
10.2.2.0
開發(fā)
.
public class Example1 {
public static void main(String[] args) {
...
String dbName = "example1";
String tableName = "stu1";
ds = new EmbeddedDataSource40();
ds.setDatabaseName(dbName);
String connectionURL = "jdbc:derby:"+dbName+";create=true";
try {
con = ds.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select * from "+tableName);
int colCount= rs.getMetaData().getColumnCount();
for (int j=0; j< colCount; j++){
System.out.print(rs.getMetaData().getColumnName(j+1)
+ "\t");
}
while (rs.next()) {
System.out.print("\n");
for (int i = 0; i < colCount; i++) {
System.out.print(rs.getString(i + 1) + "\t");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
finally{
//close connections
}
}
}
如果在
example1
數(shù)據(jù)庫的
stu1
表中有數(shù)據(jù)的話
,
編譯并運(yùn)行
Example1.java
將在控制臺獲得以下輸出:
ID NAME COURSE
1001 John Doe Statistics
1002 Jack McDonalds Linear Algebra
如果想看
DriverManager
如何自動(dòng)加載
JDBC
驅(qū)動(dòng)
,
可以將
Example1
中的:
con=ds.getConnection()
替換為:
con=DriverManager.getConnection(connectionURL)
.
該類將產(chǎn)生相同的輸出。正如您所看到的,再也不用顯式地調(diào)用
Class.forName()
了
.
異常處理
怎樣辨別一個(gè)
Java
程序的健壯與否呢
?
在我看來,異常處理機(jī)制是重要的考慮因素之一
.
一個(gè)健壯的
Java
程序可以很好地處理異常
,
并給予程序在發(fā)生異常時(shí)恢復(fù)的能力
.
而一個(gè)不健壯的程序?qū)?dǎo)致輸出錯(cuò)誤的結(jié)果甚至導(dǎo)致整個(gè)應(yīng)用的崩潰
!
JDBC 4.0
增加了一些簡單而有力的異常處理機(jī)制
,
其中值得一提的是鏈?zhǔn)疆惓#绻@個(gè)異常鏈存在的話,即可應(yīng)用增強(qiáng)了的
for
-
each
循環(huán)來獲取異常鏈
,.
下面的
Example2
類的局部結(jié)構(gòu)展示了如何應(yīng)用這種新的方法處理鏈?zhǔn)疆惓#?/span>
public class Example2 {
public static void main(String[] args) {
String dbName = "example";
String tableName = "student4";
try {
con = ds.getConnection();
stmt = con.createStatement();
rs = stmt.executeQuery("select * from " + tableName);
} catch (SQLException sx) {
for(Throwable e : sx ) {
System.err.println("Error encountered: " + e);
}
}
finally{
//close connections
}
}
}
運(yùn)行
Example2.java
,
注意
student4
并不是數(shù)據(jù)庫中實(shí)際存在的表
.
將在下列調(diào)用中產(chǎn)生鏈?zhǔn)疆惓#?/span>
rs = stmt.executeQuery("select * from " + tableName);
在實(shí)際的應(yīng)用中,需要捕捉到這些異常
,
檢測并進(jìn)行相應(yīng)的處理
.
在本例中,筆者僅將其在控制臺輸出
.
以下是輸出代碼:
for(Throwable e : sx ) {
System.err.println("Error encountered: " + e);
}
以下是類
Example2
輸出的結(jié)果
:
Error encountered: java.sql.SQLSyntaxErrorException:
Table/View 'STUDENT4' does not exist.
Error encountered: java.sql.SQLException:
Table/View 'STUDENT4' does not exist.
Exception in thread "main" java.lang.NullPointerException
at ex.Examlpe2.main(Examlpe2.java:51)
通過應(yīng)用
JDBC 4.0,
您現(xiàn)在不需太多代碼即可以獲取及遍歷異常鏈
.
在以往的版本中
,
您在遍歷異常鏈時(shí),必須手工的調(diào)用
getNextException
方法才能得到相同的效果
.
支持的數(shù)據(jù)類型
本版
JDBC
增加了一些新的數(shù)據(jù)類型,對其他的一些數(shù)據(jù)類型,則提供了更好的支持
.
筆者為
XML
被正式支持感到欣喜
,
本版中產(chǎn)生了一個(gè)新的接口
:
SQLXML
.
在筆者看來這個(gè)接口值得單獨(dú)開一個(gè)章節(jié)為其討論:
SQLXML
與
XML
的支持
SQLXML
是
SQL
中
XML
數(shù)據(jù)類型在
Java
中的表示,
XML
是
SQL
中用于表示表中
XML
數(shù)據(jù)的內(nèi)建數(shù)據(jù)類型
.
在默認(rèn)的情況下,
JDBC
驅(qū)動(dòng)將
SQLXML
指針指向
XML
數(shù)據(jù)而不是數(shù)據(jù)本身
.
SQLXML
對象在其被創(chuàng)建的事務(wù)中是穩(wěn)定的
.
在下面的
Example3
類中
,
筆者將說明如何在當(dāng)前連接中應(yīng)用
SQLXML
并更新表數(shù)據(jù)
.
public class Example3 {
public static void main(String[] args) {
...
con = ds.getConnection();
SQLXML sx= con.createSQLXML();
sx.setString("Math is Fun");
String psx ="insert into "+tableName+
" ( id, textbook) values(?,?) ";
PreparedStatement pstmt = con.prepareStatement(psx);
pstmt.setString(1,"1000");
pstmt.setSQLXML(2,sx);
pstmt.executeUpdate();
...
}
}
這個(gè)例子說明了您所能應(yīng)用的最簡單的情況
.
如果我們繼續(xù)深入研究,事情就會(huì)變得有趣得多了
.
但在我們深入討論之前
,
讓我來告訴您運(yùn)行
Example3.java
.
的結(jié)果。
非常不幸
,
我無法獲取到
SQLXML
對象,并得到了以下讓人失望的輸出:
java.sql.SQLFeatureNotSupportedException: Feature not
implemented: no details.
at org.apache.derby.impl.jdbc.SQLExceptionFactory40.
getSQLException(Unknown Source)
... ... ... ...
at ex.Example3.main(Example3.java:62)
看來
Apache Derby
并沒有提供從
Connection
中獲取
SQLXML
對象的方法
.
但至少您可以看到筆者正試圖在類
Example3
中實(shí)現(xiàn)的東西
:
我想插入一行新的數(shù)據(jù):
id
列值為
1000
textbook
列
(S
QLXML
類型
)
插入
Math is Fun
.
筆者用如下代碼段結(jié)束關(guān)于
SQLXML
的討論,這段代碼從數(shù)據(jù)庫中讀取
XML
值并將其轉(zhuǎn)化為
Document
對象
.
SQLXML sqlxml = rs.getSQLXML(column);
InputStream binaryStream = sqlxml.getBinaryStream();
DocumentBuilder parser =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = parser.parse(binaryStream);
可以把一個(gè)列的值直接轉(zhuǎn)化為
XML
文檔不是一件令人興奮的事情嗎
?
我覺得這個(gè)特性非常好
.
ROWID
數(shù)據(jù)類型
SQL
ROWID
唯一標(biāo)識了數(shù)據(jù)表中的一行,并是訪問該行的最快的方法,
本版增加了
RowId
接口以提供對
ROWID
SQL
數(shù)據(jù)類型在
Java
類中的支持
.
對大對象類型支持的增強(qiáng)
JDBC
版本
2
提供了對大的
SQL
對象如:
CLOB
,
BLOB
,
ARRAY
的支持
,
及用于添加相關(guān)接口的
Struct:
Clob
,
Blob
,
Array
, and
Struct
.
在本版的
JDBC
中增加了很多對這些對象訪問的新方法
.
筆者將在
API
變化一節(jié)中進(jìn)行詳細(xì)論述
.
支持
National Character Set
(
NCS
)
轉(zhuǎn)化
SQL:2003
提出了如下
SQL
數(shù)據(jù)類型的支持:
NCHAR
,
NVARCHAR
,
LONGNVARCHAR
,
及
NCLOB
.
其功用和
CHAR
,
VARCHAR
,
LONGVARCHAR
,
及
CLOB
類似
,其區(qū)別僅是,這些類型的文本是用
NCS
編碼的。
如果需要大量的字符處理,您可能更傾向于
NCS
數(shù)據(jù)類型而非普通的數(shù)據(jù)類型。本版
JDBC
提供了增強(qiáng)對
NCS
支持的
API.
-
在
PreparedStatement
,
CallableStatement
,
及
ResultSet
接口中增加了一些
setter
和
updater
方法以支持
NCS
轉(zhuǎn)化
.
比如方法
setNString
,
setNCharacterStream
,
及
setNClob
等等
.
-
在
SQLInput
and
SQLOutput
接口中增加了讀寫方法以支持
t
NClob
和
NString
對象
.
API
變化
JDBC 4.0
最大的變化來自于
API,
筆者在本小節(jié)對其做簡單介紹
.
Array
Array
接口增加了一個(gè)
free
方法來釋放
array
對象及其持有的資源
.
Connection
和
PooledConnection
Connection
接口現(xiàn)在提供一系列創(chuàng)建大對象的方法如
createClob
,
createBlob
等等
.
此外還有
getter
和
setter
對客戶端信息的重載方法
,
及驗(yàn)證當(dāng)前連接正確性的方法
.
PooledConnection
接口當(dāng)前提供
addStatementEventListener
和
removeStatementEventListener
兩個(gè)方法來注冊和注銷
StatementEventListener
接口
,
這個(gè)接口是在本版
JDBC
中新引入的
.
這個(gè)接口的一個(gè)實(shí)例將獲取到
S
tatement
池中
PreparedStatement
s
的變化
.
比如,在注冊以后
,
當(dāng)驅(qū)動(dòng)調(diào)用
statementClosed
方法時(shí),所有
StatementEventListener
將獲得
statement
已關(guān)閉的通知
.
DatabaseMetaData
不同的關(guān)系數(shù)據(jù)庫往往支持不同的特性
,
并通過不同的方法來實(shí)現(xiàn)這些特性
,
并可能會(huì)是用不同的數(shù)據(jù)類型
.
這將會(huì)導(dǎo)致移植性的問題,因?yàn)楦鶕?jù)實(shí)現(xiàn)的不同,無法保證代碼在所有關(guān)系數(shù)據(jù)庫上都能正確執(zhí)行
.
這樣的問題在一定程度上可以通過這個(gè)接口所獲得的信息來解決
.
比如,如果您在寫一個(gè)通過傳入
SQL
語句來建立表的代碼
.
您可能想知道在
CREATE TABLE
語句中有哪些數(shù)據(jù)類型是可用的,此時(shí)可以調(diào)用該接口中的
getTypeInfo
方法
.
本版
JDBC
增加了一些獲取信息的方法
.
在
Example4
中
,
我將通過一段代碼展示如何獲得滿足某種模式的數(shù)據(jù)庫結(jié)構(gòu)的列表。
.
con = ds.getConnection();
DatabaseMetaData dmd = con.getMetaData();
rs=dmd.getSchemas("TABLE_CAT", "SYS%");
//iterate over the rs and print to console
首先通過調(diào)用
dmd.getCatalogs
并遍歷結(jié)果集
,
得到了唯一的一個(gè)值:
TABLE_CAT
.
接著通過調(diào)用
rs=dmd.getSchemas("TABLE_CAT", "SYS%")
得到以
SYS
開頭的數(shù)據(jù)庫和表結(jié)構(gòu)
.
以下是筆者得到的結(jié)果
:
SYS
SYSCAT
SYSCS_DIAG
SYSCS_UTIL
SYSFUN
SYSIBM
SYSPROC
SYSSTAT
Scalar
函數(shù)支持
一個(gè)
scalar
函數(shù)操作預(yù)定義的輸入數(shù)據(jù)集合并返回結(jié)果
.
比如:
scalar
函數(shù)調(diào)用
ABS(number)
返回
number
的絕對值
.
這些
scalar
函數(shù)可以作為
SQL
字符串的一部分來使用
.
本版
JDBC
要求當(dāng)所依賴的關(guān)系數(shù)據(jù)庫支持以下功能時(shí):
CHAR_LENGTH
,
CHARACTER_LENGTH
,
CURRENT_DATE
,
CURRENT_TIME
,
CURRENT_TIMESTAMP
,
EXTRACT
,
OCTET_LENGTH
,
和
POSITION
,驅(qū)動(dòng)必須實(shí)現(xiàn)這些功能。
Statement
,
PreparedStatement
,
和
CallableStatement
Statement
接口當(dāng)前提供
isClosed
方法來判斷
statement
是否已關(guān)閉
,
setPoolable
用來設(shè)置是否可以被池化
,
用
isPoolable
來檢測當(dāng)前的池化狀態(tài)。
PreparedStatement
及
CallableStatement
接口現(xiàn)在提供了更多插入大對象的方法
,
通過使用
InputStream
及
Reader
等
.
Wrapper
這個(gè)版本的
API
增加了一個(gè)新的
Wrapper
接口,
來提供一種訪問資源的實(shí)例的方法
,
這可能是基于架構(gòu)的考慮
. Wrapper
模式
,
被許多的
JDBC
驅(qū)動(dòng)實(shí)現(xiàn)應(yīng)用以提供
JDBC API
之外的依賴于具體數(shù)據(jù)源的應(yīng)用
.
這個(gè)接口的主要目的是用來提供供應(yīng)商相關(guān)的功能。您可以通過調(diào)用
unwrap
方法來獲取到數(shù)據(jù)庫連接的接口實(shí)現(xiàn)的實(shí)例
.
因?yàn)檫@是一個(gè)重量級的操作
,
在使用前,應(yīng)該先調(diào)用
isWrapperFor
方法來檢測是否當(dāng)前實(shí)例是某種實(shí)現(xiàn)的一個(gè)間接或直接的
Wapper
能夠給出一個(gè)程序例子當(dāng)然是最好的,但是
Apache Derby
參考手冊
l
指出
: "JDBC 4.0
引入了
wrapped JDBC
對象的概念
...
對于
Derby
來說
,
這對
Derby
來說是沒有意義的,因?yàn)?/span>
Derby
并不做規(guī)范之外的擴(kuò)展
."
因此看來這種嘗試也就變得無甚必要了
!
結(jié)論
我們已經(jīng)分為
4
類討論了
JDBC 4.0
所做的一些改進(jìn)和新的特性,這些新特性增加了編程易用性,提高了生產(chǎn)率
.
盡管
API
規(guī)范已經(jīng)推出幾個(gè)月了
,
到筆者截稿時(shí),主流的數(shù)據(jù)庫廠商都沒有提供本版的
JDBC
驅(qū)動(dòng)
.
當(dāng)更多的供應(yīng)商開始支持
JDBC 4.0
時(shí)
—
當(dāng)然也包括您所中意的那個(gè)
—
您就可以享受
JDBC4.0
所提供的這些易用的功能了
.
最后,我認(rèn)為有一個(gè)各大數(shù)據(jù)庫廠商的支持的
JDBC
版本的列表是必要的
. Sun Developer Network (SDN)
上有一個(gè)
JDBC Data Access API
(
http://developers.sun.com/product/jdbc/drivers
)
頁提供了一份更新不太及時(shí)的列表
.
原作者信息:
Sharad Acharya has more than eight years of experience in the software engineering field in multiple business domains including supply chain, insurance, banking, and mortgage.
@2008 楊一. 版權(quán)所有. 保留所有權(quán)利