年底 Sun 公司發布了 Java Standard Edition (Java SE )的最終正式版 代號 Mustang(野馬) 跟 Tiger(Java SE )相比 Mustang 在性能方面有了不錯的提升 與 Tiger 在 API 庫方面的大幅度加強相比 雖然 Mustang 在 API 庫方面的新特性顯得不太多 但是也提供了許多實用和方便的功能 在腳本 WebService XML 編譯器 API 數據庫 JMX 網絡和 Instrumentation 方面都有不錯的新特性和功能加強 本系列 文章主要介紹 Java SE 在 API 庫方面的部分新特性 通過壹些例子和講解 幫助開發者在編程實踐當中更好的運用 Java SE 提高開發效率
本文是系列文章的第 篇 介紹了 Java SE 在數據庫編程方面的新特性
Java DB Java 裏的數據庫
新安裝了 JDK 的程序員們也許會發現 除了傳統的 bin jre 等目錄 JDK 新增了壹個名為 db 的目錄 這便是 Java 的新成員 Java DB 這是壹個純 Java 實現 開源的數據庫管理系統(DBMS) 源於 Apache 軟件基金會(ASF)名下的項目 Derby 它只有 MB 大小 對比動輒上 G 的數據庫來說可謂袖珍 但這並不妨礙 Derby 功能齊備 支持幾乎大部分的數據庫應用所需要的特性 更難能可貴的是 依托於 ASF 強大的社區力量 Derby 得到了包括 IBM 和 Sun 等大公司以及全世界優秀程序員們的支持 這也難怪 Sun 公司會選擇其 版本納入到 JDK 中 作為內嵌的數據庫 這就好像為 JDK 註入了壹股全新的活力 Java 程序員不再需要耗費大量精力安裝和配置數據庫 就能進行安全 易用 標準 並且免費的數據庫編程 在這壹章中 我們將初窺 Java DB 的世界 來探究如何使用它編寫出功能豐富的程序
Hello Java DB 內嵌模式的Derby
既然有了內嵌(embedded)的數據庫 就讓我們從壹個簡單的範例(代碼在 清單 中列出)開始 試著使用它吧 這個程序做了大多數數據庫應用都可能會做的操作 在 DBMS 中創建了壹個名為 helloDB 的數據庫 創建了壹張數據表 取名為 hellotable 向表內插入了兩條數據 然後 查詢數據並將結果打印在控制臺上 最後 刪除表和數據庫 釋放資源
清單 HelloJavaDB 的代碼
public class HelloJavaDB { public static void main(String[] args) { try { // load the driver Class forName( apache derby jdbc EmbeddedDriver ) newInstance(); System out println( Load the embedded driver ); Connection conn = null; Properties props = new Properties(); props put( user user ); props put( password user ); //create and connect the database named helloDB conn=DriverManager getConnection( jdbc:derby:helloDB;create=true props); System out println( create and connect to helloDB ); conn setAutoCommit(false); // create a table and insert o records Statement s = conn createStatement(); s execute( create table hellotable(name varchar( ) score int) ); System out println( Created table hellotable ); s execute( insert into hellotable values( Ruth Cao ) ); s execute( insert into hellotable values ( Flora Shi ) ); // list the o records ResultSet rs = s executeQuery( SELECT name score FROM hellotable ORDER BY score ); System out println( name\t\tscore ); while(rs next()) { StringBuilder builder = new StringBuilder(rs getString( )); builder append( \t ); builder append(rs getInt( )); System out println(builder toString()); } // delete the table s execute( drop table hellotable ); System out println( Dropped table hellotable ); rs close(); s close(); System out println( Closed result set and statement ); mit(); conn close(); System out println( Committed transaction and closed connection ); try { // perform a clean shutdown DriverManager getConnection( jdbc:derby:;shutdown=true ); } catch (SQLException se) { System out println( Database shut down normally ); } } catch (Throwable e) { // handle the exception } System out println( SimpleApp finished ); }}
隨後 我們在命令行(本例為 Windows 平臺 當然 其它系統下稍作改動即可)下鍵入以下命令
清單 運行 HelloJavaDB 命令
java –cp ;%JAVA_HOME%\db\lib\derby jar HelloJavaDB
程序將會按照我們預想的那樣執行 圖 是執行結果的壹部分截屏
圖 HelloJavaDB 程序的執行結果
上述的程序和以往沒什麽區別 不同的是我們不需要再為 DBMS 的配置而勞神 因為 Derby 已經自動地在當前目錄下新建了壹個名為 helloDB 的目錄 來物理地存儲數據和日誌 需要做的只是註意命名問題 在內嵌模式下驅動的名字應為 apache derby jdbc EmbeddedDriver 創建壹個新數據庫時需要在協議後加入 create=true 另外 關閉所有數據庫以及 Derby 的引擎可以使用以下代碼
清單 關閉所有數據庫及 Derby 引擎
DriverManager getConnection( jdbc:derby:;shutdown=true );
如果只想關閉壹個數據庫 那麽則可以調用
清單 關閉壹個數據庫
DriverManager getConnection( jdbc:derby:helloDB;shutdown=true );
這樣 使用嵌入模式的 Derby 維護和管理數據庫的成本接近於 這對於希望專心寫代碼的人來說不失為壹個好消息 然而有人不禁要問 既然有了內嵌模式 為什麽大多數的 DBMS 都沒有采取這樣的模式呢?不妨做壹個小實驗 當我們同時在兩個命令行窗口下運行 HelloJavaDB 程序 結果壹個的結果與剛才壹致 而另壹個卻出現了錯誤 如 圖 所示
圖 內嵌模式的局限
錯誤的原因其實很簡單 在使用內嵌模式時 Derby 本身並不會在壹個獨立的進程中 而是和應用程序壹起在同壹個 Java 虛擬機(JVM)裏運行 因此 Derby 如同應用所使用的其它 jar 文件壹樣變成了應用的壹部分 這就不難理解為什麽在 classpath 中加入 derby 的 jar 文件 我們的示例程序就能夠順利運行了 這也說明了只有壹個 JVM 能夠啟動數據庫 而兩個跑在不同 JVM 實例裏的應用自然就不能夠訪問同壹個數據庫了
鑒於上述的局限性 和來自不同 JVM 的多個連接想訪問壹個數據庫的需求 下壹節將介紹 Derby 的另壹種模式 網絡服務器(Neork Server)
網絡服務器模式
如上所述 網絡服務器模式是壹種更為傳統的客戶端/服務器模式 我們需要啟動壹個 Derby 的網絡服務器用於處理客戶端的請求 不論這些請求是來自同壹個 JVM 實例 還是來自於網絡上的另壹臺機器 同時 客戶端使用 DRDA(Distributed Relational Database Architecture)協議連接到服務器端 這是壹個由 The Open Group 倡導的數據庫交互標準 圖 說明了該模式的大體結構
由於 Derby 的開發者們努力使得網絡服務器模式與內嵌模式之間的差異變小 使得我們只需簡單地修改 清單 中的程序就可以實現 如 清單 所示 我們在 HelloJavaDB 中增添了壹個新的函數和壹些字符串變量 不難看出 新的代碼只是將壹些在 上壹節中特別指出的字符串進行了更改 驅動類為 apache derby jdbc ClientDriver 而連接數據庫的協議則變成了 jdbc:derby://localhost: / 這是壹個類似 URL 的字符串 而事實上 Derby 網絡的客戶端的連接格式為 jdbc:derby://server[:port]/databaseName[;attributeKey=value] 在這個例子中 我們使用了最簡單的本地機器作為服務器 而端口則是 Derby 默認的 端口
圖 Derby 網絡服務器模式架構
清單 網絡服務器模式下的 HelloJavaDB
public class HelloJavaDB { public static String driver = apache derby jdbc EmbeddedDriver ; public static String protocol = jdbc:derby: ; public static void main(String[] args) { // same as before } private static void parseArguments(String[] args) { if (args length == || args length > ) { return; } if (args[ ] equalsIgnoreCase( derbyclient )) { framework = derbyclient ; driver = apache derby jdbc ClientDriver ; protocol = jdbc:derby://localhost: / ; } }}
當然 僅僅有客戶端是不夠的 我們還需要啟動網絡服務器 Derby 中控制網絡服務器的類是 apache derby drda NeorkServerControl 因此鍵入以下命令即可 如果想了解 NeorkServerControl 更多的選項 只要把 start 參數去掉就可以看到幫助信息了 關於網絡服務器端的實現 都被 Derby 包含在 derbynet jar 裏
清單 啟動網絡服務器
java cp ; C:\Program Files\Java\jdk \db\lib\derby jar ; C:\Program Files\Java\jdk \db\lib\derbynet jar apache derby drda NeorkServerControl start
相對應的 網絡客戶端的實現被包含在 derbyclient jar 中 所以 只需要在 classpath 中加入該 jar 文件 修改後的客戶端就可以順利地讀取數據了 再壹次嘗試著使用兩個命令行窗口去連接數據庫 就能夠得到正確的結果了 如果不再需要服務器 那麽使用 NeorkServerControl 的 shutdown 參數就能夠關閉服務器
更多
至此 文章介紹了 Java SE 中的新成員 Java DB(Derby) 也介紹了如何在內嵌模式以及網絡服務器模式下使用 Java DB 當然這只是淺嘗輒止 更多高級的選項還需要在 Sun 和 Derby 的文檔中尋找 在這壹章的最後 我們將簡單介紹幾個 Java DB 的小工具來加快開發速度 它們都位於 apache derby tools 包內 在開發過程中需要獲取信息或者測試可以用到
ij 壹個用來運行 SQL 腳本的工具 dblook 為 Derby 數據庫作模式提取(Schema extraction) 生成 DDL 的工具 sysinfo 顯示系統以及 Derby 信息的工具類JDBC 新功能 新 API
如果說上壹章介紹了 Java 中的壹個新成員 它本來就存在 但是沒有被加入進 JDK 那麽這壹章 我們將關註在 JDBC 中又增加了哪些新功能以及與之相對應的新 API
自動加載驅動
在 JDBC 之前 編寫 JDBC 程序都需要加上以下這句有點醜陋的代碼
清單 註冊 JDBC 驅動
Class forName( apache derby jdbc EmbeddedDriver ) newInstance();
Java sql DriverManager 的內部實現機制決定了這樣代碼的出現 只有先通過 Class forName 找到特定驅動的 class 文件 DriverManager getConnection 方法才能順利地獲得 Java 應用和數據庫的連接 這樣的代碼為編寫程序增加了不必要的負擔 JDK 的開發者也意識到了這壹點 從 Java 開始 應用程序不再需要顯式地加載驅動程序了 DriverManager 開始能夠自動地承擔這項任務 作為試驗 我們可以將 清單 中的相關代碼刪除 重新編譯後在 JRE 下運行 結果和原先的程序壹樣
好奇的讀者也許會問 DriverManager 為什麽能夠做到自動加載呢?這就要歸功於壹種被稱為 Service Provider 的新機制 熟悉 Java 安全編程的程序員可能對其已經是司空見慣 而它現在又出現在 JDBC 模塊中 JDBC 的規範規定 所有 JDBC 的驅動 jar 文件必須包含壹個 java sql Driver 它位於 jar 文件的 META INF/services 目錄下 這個文件裏每壹行便描述了壹個對應的驅動類 其實 編寫這個文件的方式和編寫壹個只有關鍵字(key)而沒有值(value)的 properties 文件類似 同樣地 # 之後的文字被認為是註釋 有了這樣的描述 DriverManager 就可以從當前在 CLASSPATH 中的驅動文件中找到 它應該去加載哪些類 而如果我們在 CLASSPATH 裏沒有任何 JDBC 的驅動文件的情況下 調用 清單 中的代碼會輸出壹個 sun jdbc odbc JdbcOdbcDriver 類型的對象 而仔細瀏覽 JDK 的目錄 這個類型正是在 %JAVA_HOME%/jre/lib/resources jar 的 META INF/services 目錄下的 java sql Driver 文件中描述的 也就是說 這是 JDK 中默認的驅動 而如果開發人員想使得自己的驅動也能夠被 DriverManager 找到 只需要將對應的 jar 文件加入到 CLASSPATH 中就可以了 當然 對於那些 JDBC 之前的驅動文件 我們還是只能顯式地去加載了
清單 羅列本地機器上的 JDBC 驅動
Enumeration<Driver> drivers = DriverManager getDrivers();while(drivers hasMoreElements()) { System out println(drivers nextElement());}
RowId
熟悉 DB Oracle 等大型 DBMS 的人壹定不會對 ROWID 這個概念陌生 它是數據表中壹個 隱藏 的列 是每壹行獨壹無二的標識 表明這壹行的物理或者邏輯位置 由於 ROWID 類型的廣泛使用 Java SE 中新增了 java sql RowId 的數據類型 允許 JDBC 程序能夠訪問 SQL 中的 ROWID 類型 誠然 不是所有的 DBMS 都支持 ROWID 類型 即使支持 不同的 ROWID 也會有不同的生命周期 因此使用 DatabaseMetaData getRowIdLifetime 來判斷類型的生命周期不失為壹項良好的實踐經驗 我們在 清單 的程序獲得連接之後增加以下代碼 便可以了解 ROWID 類型的支持情況
清單 了解 ROWID 類型的支持情況DatabaseMetaData meta = conn getMetaData();System out println(meta getRowIdLifetime());
Java SE 的 API 規範中 java sql RowIdLifetime 規定了 種不同的生命周期 ROWID_UNSUPPORTED ROWID_VALID_FOREVER ROWID_VALID_OTHER ROWID_VALID_SESSION 和 ROWID_VALID_TRANSACTION 從字面上不難理解它們表示了不支持 ROWID ROWID 永遠有效等等 具體的信息 還可以參看相關的 JavaDoc 讀者可以嘗試著連接 Derby 進行試驗 會發現運行結果是 ROWID_UNSUPPORTED 即 Derby 並不支持 ROWID
既然提供了新的數據類型 那麽壹些相應的獲取 更新數據表內容的新 API 也在 Java 中被添加進來 和其它已有的類型壹樣 在得到 ResultSet 或者 CallableStatement 之後 調用 get/set/update 方法得到/設置/更新 RowId 對象 示例的代碼如 清單 所示
清單 獲得/設置 RowId 對象
// Initialize a PreparedStatementPreparedStatement pstmt = connection prepareStatement( SELECT rowid name score FROM hellotable WHERE rowid = ? );// Bind rowid into prepared statement pstmt setRowId( rowid);// Execute the statementResultSet rset = pstmt executeQuery(); // List the recordswhile(rs next()) { RowId id = rs getRowId( ); // get the immutable rowid object String name = rs getString( ); int score = rs getInt( );}
鑒於不同 DBMS 的不同實現 RowID 對象通常在不同的數據源(datasource)之間並不是可移植的 因此 JDBC 的 API 規範並不建議從連接 A 取出壹個 RowID 對象 將它用在連接 B 中 以避免不同系統的差異而帶來的難以解釋的錯誤 而至於像 Derby 這樣不支持 RowId 的 DBMS 程序將直接在 setRowId 方法處拋出 SQLFeatureNotSupportedException
SQLXML
SQL 標準引入了 SQL/XML 作為 SQL 標準的擴展 SQL/XML 定義了 SQL 語言怎樣和 XML 交互 如何創建 XML 數據 如何在 SQL 語句中嵌入 XQuery 表達式等等 作為 JDBC 的壹部分 Java 增加了 java sql SQLXML 的類型 JDBC 應用程序可以利用該類型初始化 讀取 存儲 XML 數據 java sql Connection createSQLXML 方法就可以創建壹個空白的 SQLXML 對象 當獲得這個對象之後 便可以利用 setString setBinaryStream setCharacterStream 或者 setResult 等方法來初始化所表示的 XML 數據 以 setCharacterStream 為例 清單 表示了壹個 SQLXML 對象如何獲取 java io Writer 對象 從外部的 XML 文件中逐行讀取內容 從而完成初始化
清單 利用 setCharacterStream 方法來初始化 SQLXML 對象
SQLXML xml = con createSQLXML();Writer writer = xml setCharacterStream();BufferedReader reader = new BufferedReader(new FileReader( test xml ));String line= null;while((line = reader readLine() != null) { writer write(line);}
由於 SQLXML 對象有可能與各種外部的資源有聯系 並且在壹個事務中壹直持有這些資源 為了防止應用程序耗盡資源 Java 提供了 free 方法來釋放其資源 類似的設計在 java sql Array Clob 中都有出現
至於如何使用 SQLXML 與數據庫進行交互 其方法與其它的類型都十分相似 可以參照 RowId 壹節 中的例子在 Java SE 的 API 規範中找到 SQLXML 中對應的 get/set/update 方法構建類似的程序 此處不再贅述
SQLExcpetion 的增強
在 Java SE 之前 有關 JDBC 的異常類型不超過 個 這似乎已經不足以描述日漸復雜的數據庫異常情況 因此 Java SE 的設計人員對以 java sql SQLException 為根的異常體系作了大幅度的改進 首先 SQLException 新實現了 Iterable<Throwable> 接口 清單 實現了 清單 程序的異常處理機制 這樣簡潔地遍歷了每壹個 SQLException 和它潛在的原因(cause)
清單 SQLException 的 for each loop
// Java codecatch (Throwable e) { if (e instanceof SQLException) { for(Throwable ex : (SQLException)e ){ System err println(ex toString()); } }}
此外 圖 表示了全部的 SQLException 異常體系 除去原有的 SQLException 的子類 Java 中新增的異常類被分為 種 SQLReoverableException SQLNonTransientException SQLTransientException 在 SQLNonTransientException 和 SQLTransientException 之下還有若幹子類 詳細地區分了 JDBC 程序中可能出現的各種錯誤情況 大多數子類都會有對應的標準 SQLState 值 很好地將 SQL 標準和 Java 類庫結合在壹起
圖 SQLException 異常體系
在眾多的異常類中 比較常見的有 SQLFeatureNotSupportedException 用來表示 JDBC 驅動不支持某項 JDBC 的特性 例如在 Derby 下運行 清單 中的程序 就可以發現 Derby 的驅動並不支持 RowId 的特性 另外值得壹提的是 SQLClientInfoException 直接繼承自 SQLException 表示當壹些客戶端的屬性不能被設置在壹個數據庫連接時所發生的異常
小結 更多新特性與展望
在本文中 我們已經向讀者介紹了 Java SE 中 JDBC 最重要的壹些新特性 它們包括嵌在 JDK 中的 Java DB (Derby)和 JDBC 的壹部分 當然 還有很多本文還沒有覆蓋到的新特性 比如增加了對 SQL 語言中 NCHAR NVARCHAR LONGNVARCHAR 和 NCLOB 類型的支持 在數據庫連接池的環境下為管理 Statement 對象提供更多靈活 便利的方法等
lishixinzhi/Article/program/Java/JSP/201311/19343