Java SE 6 新特性: Java DB 和 JDBC 4.0
http://www.ibm.com/developerworks/cn/java/j-lo-jse65/index.html
長久以來,由于大量(甚至幾乎所有)的 Java 應(yīng)用都依賴于數(shù)據(jù)庫,如何使用 Java 語言高效、可靠、簡潔地訪問數(shù)據(jù)庫一直是程序員們津津樂道的話題。新發(fā)布的 Java SE 6 也在這方面更上層樓,為編程人員提供了許多好用的新特性。其中最顯著的,莫過于 Java SE 6 擁有了一個內(nèi)嵌的 100% 用 Java 語言編寫的數(shù)據(jù)庫系統(tǒng)。并且,Java 6 開始支持 JDBC 4.0 的一系列新功能和屬性。這樣,Java SE 在對持久數(shù)據(jù)的訪問上就顯得更為易用和強大了。
Java DB:Java 6 里的數(shù)據(jù)庫
新安裝了 JDK 6 的程序員們也許會發(fā)現(xiàn),除了傳統(tǒng)的 bin、jre 等目錄,JDK 6 新增了一個名為 db 的目錄。這便是 Java 6 的新成員:Java DB。這是一個純 Java 實現(xiàn)、開源的數(shù)據(jù)庫管理系統(tǒng)(DBMS),源于 Apache 軟件基金會(ASF)名下的項目 Derby。它只有 2MB 大小,對比動輒上 G 的數(shù)據(jù)庫來說可謂袖珍。但這并不妨礙 Derby 功能齊備,支持幾乎大部分的數(shù)據(jù)庫應(yīng)用所需要的特性。更難能可貴的是,依托于 ASF 強大的社區(qū)力量,Derby 得到了包括 IBM 和 Sun 等大公司以及全世界優(yōu)秀程序員們的支持。這也難怪 Sun 公司會選擇其 10.2.2 版本納入到 JDK 6 中,作為內(nèi)嵌的數(shù)據(jù)庫。這就好像為 JDK 注入了一股全新的活力:Java 程序員不再需要耗費大量精力安裝和配置數(shù)據(jù)庫,就能進行安全、易用、標準、并且免費的數(shù)據(jù)庫編程。在這一章中,我們將初窺 Java DB 的世界,來探究如何使用它編寫出功能豐富的程序。
Hello, Java DB:內(nèi)嵌模式的 Derby
既然有了內(nèi)嵌(embedded)的數(shù)據(jù)庫,就讓我們從一個簡單的范例(代碼在?清單 1?中列出)開始,試著使用它吧。這個程序做了大多數(shù)數(shù)據(jù)庫應(yīng)用都可能會做的操作:在 DBMS 中創(chuàng)建了一個名為 helloDB 的數(shù)據(jù)庫;創(chuàng)建了一張數(shù)據(jù)表,取名為 hellotable;向表內(nèi)插入了兩條數(shù)據(jù);然后,查詢數(shù)據(jù)并將結(jié)果打印在控制臺上;最后,刪除表和數(shù)據(jù)庫,釋放資源。
清單 1. HelloJavaDB 的代碼
public class HelloJavaDB {public static void main(String[] args) {try { // load the driverClass.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();System.out.println("Load the embedded driver");Connection conn = null;Properties props = new Properties();props.put("user", "user1"); props.put("password", "user1");//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 two recordsStatement s = conn.createStatement();s.execute("create table hellotable(name varchar(40), score int)");System.out.println("Created table hellotable");s.execute("insert into hellotable values('Ruth Cao', 86)");s.execute("insert into hellotable values ('Flora Shi', 92)");// list the two recordsResultSet 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(1));builder.append("\t");builder.append(rs.getInt(2));System.out.println(builder.toString());}// delete the tables.execute("drop table hellotable");System.out.println("Dropped table hellotable");rs.close();s.close();System.out.println("Closed result set and statement");conn.commit();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 平臺,當然,其它系統(tǒng)下稍作改動即可)下鍵入以下命令:
清單 2. 運行 HelloJavaDB 命令
java –cp .;%JAVA_HOME%\db\lib\derby.jar HelloJavaDB程序?qū)凑瘴覀冾A想的那樣執(zhí)行,圖 1?是執(zhí)行結(jié)果的一部分截屏:
圖 1. HelloJavaDB 程序的執(zhí)行結(jié)果
上述的程序和以往沒什么區(qū)別。不同的是我們不需要再為 DBMS 的配置而勞神,因為 Derby 已經(jīng)自動地在當前目錄下新建了一個名為 helloDB 的目錄,來物理地存儲數(shù)據(jù)和日志。需要做的只是注意命名問題:在內(nèi)嵌模式下驅(qū)動的名字應(yīng)為org.apache.derby.jdbc.EmbeddedDriver;創(chuàng)建一個新數(shù)據(jù)庫時需要在協(xié)議后加入?create=true。另外,關(guān)閉所有數(shù)據(jù)庫以及 Derby 的引擎可以使用以下代碼:
清單 3. 關(guān)閉所有數(shù)據(jù)庫及 Derby 引擎
DriverManager.getConnection("jdbc:derby:;shutdown=true");如果只想關(guān)閉一個數(shù)據(jù)庫,那么則可以調(diào)用:
清單 4. 關(guān)閉一個數(shù)據(jù)庫
DriverManager.getConnection("jdbc:derby:helloDB;shutdown=true ");這樣,使用嵌入模式的 Derby 維護和管理數(shù)據(jù)庫的成本接近于 0。這對于希望專心寫代碼的人來說不失為一個好消息。然而有人不禁要問:既然有了內(nèi)嵌模式,為什么大多數(shù)的 DBMS 都沒有采取這樣的模式呢?不妨做一個小實驗。當我們同時在兩個命令行窗口下運行 HelloJavaDB 程序。結(jié)果一個的結(jié)果與剛才一致,而另一個卻出現(xiàn)了錯誤,如?圖 2?所示。
圖 2. 內(nèi)嵌模式的局限
錯誤的原因其實很簡單:在使用內(nèi)嵌模式時,Derby 本身并不會在一個獨立的進程中,而是和應(yīng)用程序一起在同一個 Java 虛擬機(JVM)里運行。因此,Derby 如同應(yīng)用所使用的其它 jar 文件一樣變成了應(yīng)用的一部分。這就不難理解為什么在 classpath 中加入 derby 的 jar 文件,我們的示例程序就能夠順利運行了。這也說明了只有一個 JVM 能夠啟動數(shù)據(jù)庫:而兩個跑在不同 JVM 實例里的應(yīng)用自然就不能夠訪問同一個數(shù)據(jù)庫了。
鑒于上述的局限性,和來自不同 JVM 的多個連接想訪問一個數(shù)據(jù)庫的需求,下一節(jié)將介紹 Derby 的另一種模式:網(wǎng)絡(luò)服務(wù)器(Network Server)。
網(wǎng)絡(luò)服務(wù)器模式
如上所述,網(wǎng)絡(luò)服務(wù)器模式是一種更為傳統(tǒng)的客戶端/服務(wù)器模式。我們需要啟動一個 Derby 的網(wǎng)絡(luò)服務(wù)器用于處理客戶端的請求,不論這些請求是來自同一個 JVM 實例,還是來自于網(wǎng)絡(luò)上的另一臺機器。同時,客戶端使用 DRDA(Distributed Relational Database Architecture)協(xié)議連接到服務(wù)器端。這是一個由 The Open Group 倡導的數(shù)據(jù)庫交互標準。圖 3?說明了該模式的大體結(jié)構(gòu)。
由于 Derby 的開發(fā)者們努力使得網(wǎng)絡(luò)服務(wù)器模式與內(nèi)嵌模式之間的差異變小,使得我們只需簡單地修改?清單 1?中的程序就可以實現(xiàn)。如?清單 5所示,我們在 HelloJavaDB 中增添了一個新的函數(shù)和一些字符串變量。不難看出,新的代碼只是將一些在 上一節(jié)中特別指出的字符串進行了更改:驅(qū)動類為?org.apache.derby.jdbc.ClientDriver,而連接數(shù)據(jù)庫的協(xié)議則變成了?jdbc:derby://localhost:1527/。這是一個類似 URL 的字符串,而事實上,Derby 網(wǎng)絡(luò)的客戶端的連接格式為:jdbc:derby://server[:port]/databaseName[;attributeKey=value]。在這個例子中,我們使用了最簡單的本地機器作為服務(wù)器,而端口則是 Derby 默認的 1527 端口。
圖 3. Derby 網(wǎng)絡(luò)服務(wù)器模式架構(gòu)
清單 5. 網(wǎng)絡(luò)服務(wù)器模式下的 HelloJavaDB
public class HelloJavaDB {public static String driver = "org.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 == 0 || args.length > 1) {return;}if (args[0].equalsIgnoreCase("derbyclient")) {framework = "derbyclient";driver = "org.apache.derby.jdbc.ClientDriver";protocol = "jdbc:derby://localhost:1527/";}} }當然,僅僅有客戶端是不夠的,我們還需要啟動網(wǎng)絡(luò)服務(wù)器。Derby 中控制網(wǎng)絡(luò)服務(wù)器的類是org.apache.derby.drda.NetworkServerControl,因此鍵入以下命令即可。如果想了解 NetworkServerControl 更多的選項,只要把start?參數(shù)去掉就可以看到幫助信息了。關(guān)于網(wǎng)絡(luò)服務(wù)器端的實現(xiàn),都被 Derby 包含在 derbynet.jar 里。
清單 6. 啟動網(wǎng)絡(luò)服務(wù)器
java -cp .;"C:\Program Files\Java\jdk1.6.0\db\lib\derby.jar"; "C:\Program Files\Java\jdk1.6.0\db\lib\derbynet.jar" org.apache.derby.drda.NetworkServerControl start相對應(yīng)的,網(wǎng)絡(luò)客戶端的實現(xiàn)被包含在 derbyclient.jar 中。所以,只需要在 classpath 中加入該 jar 文件,修改后的客戶端就可以順利地讀取數(shù)據(jù)了。再一次嘗試著使用兩個命令行窗口去連接數(shù)據(jù)庫,就能夠得到正確的結(jié)果了。如果不再需要服務(wù)器,那么使用 NetworkServerControl 的 shutdown 參數(shù)就能夠關(guān)閉服務(wù)器。
更多
至此,文章介紹了 Java SE 6 中的新成員:Java DB(Derby),也介紹了如何在內(nèi)嵌模式以及網(wǎng)絡(luò)服務(wù)器模式下使用 Java DB。當然這只是淺嘗輒止,更多高級的選項還需要在 Sun 和 Derby 的文檔中尋找。在這一章的最后,我們將簡單介紹幾個 Java DB 的小工具來加快開發(fā)速度。它們都位于 org.apache.derby.tools 包內(nèi),在開發(fā)過程中需要獲取信息或者測試可以用到。
- ij:一個用來運行 SQL 腳本的工具;
- dblook:為 Derby 數(shù)據(jù)庫作模式提取(Schema extraction),生成 DDL 的工具;
- sysinfo:顯示系統(tǒng)以及 Derby 信息的工具類;
回頁首
JDBC 4.0:新功能,新 API
如果說上一章介紹了 Java 6 中的一個新成員,它本來就存在,但是沒有被加入進 JDK。那么這一章,我們將關(guān)注在 JDBC 4.0 中又增加了哪些新功能以及與之相對應(yīng)的新 API。
自動加載驅(qū)動
在 JDBC 4.0 之前,編寫 JDBC 程序都需要加上以下這句有點丑陋的代碼:
清單 7. 注冊 JDBC 驅(qū)動
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();Java.sql.DriverManager?的內(nèi)部實現(xiàn)機制決定了這樣代碼的出現(xiàn)。只有先通過?Class.forName?找到特定驅(qū)動的 class 文件,DriverManager.getConnection?方法才能順利地獲得 Java 應(yīng)用和數(shù)據(jù)庫的連接。這樣的代碼為編寫程序增加了不必要的負擔,JDK 的開發(fā)者也意識到了這一點。從 Java 6 開始,應(yīng)用程序不再需要顯式地加載驅(qū)動程序了,DriverManager 開始能夠自動地承擔這項任務(wù)。作為試驗,我們可以將?清單 1?中的相關(guān)代碼刪除,重新編譯后在 JRE 6.0 下運行,結(jié)果和原先的程序一樣。
好奇的讀者也許會問,DriverManager 為什么能夠做到自動加載呢?這就要歸功于一種被稱為 Service Provider 的新機制。熟悉 Java 安全編程的程序員可能對其已經(jīng)是司空見慣,而它現(xiàn)在又出現(xiàn)在 JDBC 模塊中。JDBC 4.0 的規(guī)范規(guī)定,所有 JDBC 4.0 的驅(qū)動 jar 文件必須包含一個?java.sql.Driver,它位于 jar 文件的 META-INF/services 目錄下。這個文件里每一行便描述了一個對應(yīng)的驅(qū)動類。其實,編寫這個文件的方式和編寫一個只有關(guān)鍵字(key)而沒有值(value)的 properties 文件類似。同樣地,‘#’之后的文字被認為是注釋。有了這樣的描述,DriverManager 就可以從當前在 CLASSPATH 中的驅(qū)動文件中找到,它應(yīng)該去加載哪些類。而如果我們在 CLASSPATH 里沒有任何 JDBC 4.0 的驅(qū)動文件的情況下,調(diào)用?清單 8?中的代碼會輸出一個?sun.jdbc.odbc.JdbcOdbcDriver?類型的對象。而仔細瀏覽 JDK 6 的目錄,這個類型正是在?%JAVA_HOME%/jre/lib/resources.jar?的 META-INF/services 目錄下的?java.sql.Driver?文件中描述的。也就是說,這是 JDK 中默認的驅(qū)動。而如果開發(fā)人員想使得自己的驅(qū)動也能夠被 DriverManager 找到,只需要將對應(yīng)的 jar 文件加入到 CLASSPATH 中就可以了。當然,對于那些 JDBC 4.0 之前的驅(qū)動文件,我們還是只能顯式地去加載了。
清單 8. 羅列本地機器上的 JDBC 驅(qū)動
Enumeration<Driver> drivers = DriverManager.getDrivers();while(drivers.hasMoreElements()) {System.out.println(drivers.nextElement()); }RowId
熟悉 DB2、Oracle 等大型 DBMS 的人一定不會對 ROWID 這個概念陌生:它是數(shù)據(jù)表中一個“隱藏”的列,是每一行獨一無二的標識,表明這一行的物理或者邏輯位置。由于 ROWID 類型的廣泛使用,Java SE 6 中新增了?java.sql.RowId?的數(shù)據(jù)類型,允許 JDBC 程序能夠訪問 SQL 中的 ROWID 類型。誠然,不是所有的 DBMS 都支持 ROWID 類型。即使支持,不同的 ROWID 也會有不同的生命周期。因此使用DatabaseMetaData.getRowIdLifetime?來判斷類型的生命周期不失為一項良好的實踐經(jīng)驗。我們在?清單 1?的程序獲得連接之后增加以下代碼,便可以了解 ROWID 類型的支持情況。
清單 9. 了解 ROWID 類型的支持情況
DatabaseMetaData meta = conn.getMetaData(); System.out.println(meta.getRowIdLifetime());Java SE 6 的 API 規(guī)范中,java.sql.RowIdLifetime?規(guī)定了 5 種不同的生命周期:ROWID_UNSUPPORTED、ROWID_VALID_FOREVER、ROWID_VALID_OTHER、ROWID_VALID_SESSION?和ROWID_VALID_TRANSACTION。從字面上不難理解它們表示了不支持 ROWID、ROWID 永遠有效等等。具體的信息,還可以參看相關(guān)的 JavaDoc。讀者可以嘗試著連接 Derby 進行試驗,會發(fā)現(xiàn)運行結(jié)果是?ROWID_UNSUPPORTED?,即 Derby 并不支持 ROWID。
既然提供了新的數(shù)據(jù)類型,那么一些相應(yīng)的獲取、更新數(shù)據(jù)表內(nèi)容的新 API 也在 Java 6 中被添加進來。和其它已有的類型一樣,在得到ResultSet?或者?CallableStatement?之后,調(diào)用 get/set/update 方法得到/設(shè)置/更新 RowId 對象,示例的代碼如?清單 10?所示。
清單 10. 獲得/設(shè)置 RowId 對象
// Initialize a PreparedStatement PreparedStatement pstmt = connection.prepareStatement("SELECT rowid, name, score FROM hellotable WHERE rowid = ?"); // Bind rowid into prepared statement. pstmt.setRowId(1, rowid); // Execute the statement ResultSet rset = pstmt.executeQuery(); // List the records while(rs.next()) {RowId id = rs.getRowId(1); // get the immutable rowid objectString name = rs.getString(2);int score = rs.getInt(3); }鑒于不同 DBMS 的不同實現(xiàn),RowID 對象通常在不同的數(shù)據(jù)源(datasource)之間并不是可移植的。因此 JDBC 4.0 的 API 規(guī)范并不建議從連接 A 取出一個 RowID 對象,將它用在連接 B 中,以避免不同系統(tǒng)的差異而帶來的難以解釋的錯誤。而至于像 Derby 這樣不支持 RowId 的 DBMS,程序?qū)⒅苯釉?setRowId 方法處拋出?SQLFeatureNotSupportedException。
SQLXML
SQL:2003 標準引入了 SQL/XML,作為 SQL 標準的擴展。SQL/XML 定義了 SQL 語言怎樣和 XML 交互:如何創(chuàng)建 XML 數(shù)據(jù);如何在 SQL 語句中嵌入 XQuery 表達式等等。作為 JDBC 4.0 的一部分,Java 6 增加了?java.sql.SQLXML?的類型。JDBC 應(yīng)用程序可以利用該類型初始化、讀取、存儲 XML 數(shù)據(jù)。java.sql.Connection.createSQLXML?方法就可以創(chuàng)建一個空白的 SQLXML 對象。當獲得這個對象之后,便可以利用?setString、setBinaryStream、setCharacterStream?或者?setResult?等方法來初始化所表示的 XML 數(shù)據(jù)。以setCharacterStream?為例,清單 11?表示了一個 SQLXML 對象如何獲取?java.io.Writer?對象,從外部的 XML 文件中逐行讀取內(nèi)容,從而完成初始化。
清單 11. 利用 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 對象有可能與各種外部的資源有聯(lián)系,并且在一個事務(wù)中一直持有這些資源。為了防止應(yīng)用程序耗盡資源,Java 6 提供了 free 方法來釋放其資源。類似的設(shè)計在?java.sql.Array、Clob?中都有出現(xiàn)。
至于如何使用 SQLXML 與數(shù)據(jù)庫進行交互,其方法與其它的類型都十分相似。可以參照?RowId 一節(jié)?中的例子在 Java SE 6 的 API 規(guī)范中找到 SQLXML 中對應(yīng)的 get/set/update 方法構(gòu)建類似的程序,此處不再贅述。
SQLExcpetion 的增強
在 Java SE 6 之前,有關(guān) JDBC 的異常類型不超過 10 個。這似乎已經(jīng)不足以描述日漸復雜的數(shù)據(jù)庫異常情況。因此,Java SE 6 的設(shè)計人員對以?java.sql.SQLException?為根的異常體系作了大幅度的改進。首先,SQLException 新實現(xiàn)了?Iterable<Throwable>?接口。清單 12?實現(xiàn)了?清單 1?程序的異常處理機制。這樣簡潔地遍歷了每一個 SQLException 和它潛在的原因(cause)。
清單 12. SQLException 的 for-each loop
// Java 6 code catch (Throwable e) {if (e instanceof SQLException) {for(Throwable ex : (SQLException)e ){System.err.println(ex.toString());}} }此外,圖 4?表示了全部的 SQLException 異常體系。除去原有的 SQLException 的子類,Java 6 中新增的異常類被分為 3 種:SQLReoverableException、SQLNonTransientException、SQLTransientException。在?SQLNonTransientException?和SQLTransientException?之下還有若干子類,詳細地區(qū)分了 JDBC 程序中可能出現(xiàn)的各種錯誤情況。大多數(shù)子類都會有對應(yīng)的標準SQLState?值,很好地將 SQL 標準和 Java 6 類庫結(jié)合在一起。
圖 4. SQLException 異常體系
在眾多的異常類中,比較常見的有?SQLFeatureNotSupportedException,用來表示 JDBC 驅(qū)動不支持某項 JDBC 的特性。例如在 Derby 下運行?清單 10?中的程序,就可以發(fā)現(xiàn) Derby 的驅(qū)動并不支持 RowId 的特性。另外值得一提的是,SQLClientInfoException?直接繼承自 SQLException,表示當一些客戶端的屬性不能被設(shè)置在一個數(shù)據(jù)庫連接時所發(fā)生的異常。
回頁首
小結(jié):更多新特性與展望
在本文中,我們已經(jīng)向讀者介紹了 Java SE 6 中 JDBC 最重要的一些新特性:它們包括嵌在 JDK 中的 Java DB (Derby)和 JDBC 4.0 的一部分。當然,還有很多本文還沒有覆蓋到的新特性。比如增加了對 SQL 語言中?NCHAR、NVARCHAR、LONGNVARCHAR?和?NCLOB?類型的支持;在數(shù)據(jù)庫連接池的環(huán)境下為管理?Statement?對象提供更多靈活、便利的方法等。
此外,在 Java SE 6 的 beta 版中,曾經(jīng)將 Annotation Query 的特性包含進來。這項特性定義了一系列 Query 和 DataSet 接口,程序員可以通過撰寫一些 Annotation 來自定義查詢并獲得定制的數(shù)據(jù)集結(jié)果。但是,由于這一特性的參考實現(xiàn)最終不能滿足 JDK 的質(zhì)量需求,Sun 公司忍痛割愛,取消了在 Java SE 6 中發(fā)布其的計劃。我們有理由相信,在以后的 JDK 版本中,這一特性以及更多新的功能將被包含進來,利用 Java 語言構(gòu)建數(shù)據(jù)庫的應(yīng)用也會變得更為自然、順暢。
轉(zhuǎn)載于:https://www.cnblogs.com/duanxz/p/3666784.html
總結(jié)
以上是生活随笔為你收集整理的Java SE 6 新特性: Java DB 和 JDBC 4.0的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux自动删除n天前日志
- 下一篇: iOS tableviewcell重用机