簡介 眾所周知,JDBC(Java數據庫連接)是Java 2企業版的重要組成部分。它是基於SQL層的API。通過把SQL語句嵌入JDBC接口的方法中,用戶可以通過Java程序執行幾乎所有的數據庫操作。JDBC只提供了接口,具體的類的實現要求數據庫的設計者完成。
<!-- frame contents -->
<!-- /frame contents -->
通過生成這些接口的實例,即使對於不同的數據庫,Java程序也可以正確地執行SQL調用。所以對於程序員來說,不必把注重力放在如何向數據庫發送SQL指令,因為程序員需要了解和用到的只是JDBC的接口,只有在極少數情況下會用到面向特定數據庫的類,例如程序員希望使用Oracle的擴展API。
在JDBC程序中,首先需要做的是實現與數據庫的連接。在示例程序中,我們使用的是ORACLE8i的JDBC包。連接數據庫通常需要實現以下幾個步驟:
1. 注冊數據庫驅動程序(driver)。可以通過調用java.sql.DriverManager類的registerDriver方法顯式注冊驅動程序,也可以通過加載數據庫驅動程序類隱式注冊驅動程序。例如我們希望向虛擬機注冊Oracle8i JDBC驅動程序
// 顯式注冊
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
// 隱式注冊
Class.forName(“oracle.jdbc.driver.OracleDriver”);
關於虛擬機如何自動注冊通過類加載器(ClassLoader)加載的數據庫驅動程序超過了本文討論的范圍,在此不做具體討論。
2. 建立連接。調用java.sql.DriverManager類的getConnection()方法可以建立與數據庫的連接。GetConnection()方法返回一個Connection對象。需要注重的是,getConnection()方法會自動從數據庫驅動程序注冊表中選擇一個最合適的驅動程序。
3. 建立連接後,答應自動更新(AutoCommit)。調用java.sql.Connection接口的serAutoCommit()方法可以設定當程序向數據庫發出一條SQL指令後,數據庫是否立即更新。
下面是一個具體的實例。在該實例中,作為getConnection()方法參數的url使用的是Net8 keyWord-value pair格式。當然也可以使用普通格式。數據庫安裝在名為Chicago的服務器上,使用的協議是TCP協議,使用的端口是1521,數據庫的SID是chidb,使用的數據庫驅動程序是Oracle JDBC Thin驅動程序。
import java.sql.*;
// 初始化常數
private static String url =
“jdbc:oracle:thin:@(description=(address=(host=Chicago)” +
“(protocol=tcp)(port=1521))(connect_data=(sid=chidb)))”;
// 也可以設定url為“jdbc:oracle:thin:@ Chicago:1521:chidb”
private static String username = “guest”;
private static String password = “guest”;
try
{
// 注冊數據庫
Class.forName(“oracle.jdbc.driver.OracleDriver”);
// 建立連接
Connection conn =
DriverManager.getConnection(url, username, password);
// 答應自動更新
Conn.setAutoCommit(true);
}
catch(ClassNotFoundException e )
{
e.printStackTrace();
}
catch(SQLException e)
{
e.printStackTrace();
}
從實際應用的角度出發,我們可以看出采取這種方式連接到數據庫存在幾個問題。第一是安全性問題,由於程序代碼中包含用戶名和密碼,其他人假如能得到bytecode,可以通過反編譯工具獲得用戶名和密碼。第二是代碼的可移植性問題。假如希望連接的數據庫名稱或用戶名有所更改,程序員需要修改源程序,然後把修改過的程序發送給用戶。也就是說,軟件無法脫離數據庫獨立存在。這樣不僅會大大提高軟件的成本,也不利於軟件本身的發展。還可能出現這樣的情況:在某些情況下,提供數據的機構不希望數據庫的用戶名和密碼讓編寫程序的程序員知道知道。這樣就提出了一個問題,如何使Java和數據庫之間建立連接時隱藏一些敏感的信息。進入討論組討論。
數據源(Data Source)及JNDI
數據源是在JDBC 2.0中引入的一個概念。在JDBC 2.0擴展包中定義了javax.sql.DataSource接口來描述這個概念。假如用戶希望建立一個數據庫連接,通過查詢在JNDI服務中的數據源,可以從數據源中獲取相應的數據庫連接。
<!-- frame contents -->
<!-- /frame contents -->
這樣用戶就只需要提供一個邏輯名稱(Logic Name),而不是數據庫登錄的具體細節。
在這裡有必要簡單介紹一下JNDI。JNDI的全稱是Java Naming and Directory Interface, 可以理解為Java名稱和目錄服務接口。JNDI向應用程序提供了一個查詢和使用遠程服務的機制。這些服務可以是任何企業服務。對於JDBC應用程序來說,JNDI提供的是數據庫連接服務。當然JNDI也可以向數據庫提供其他服務,但是這超出了本文范圍,在此不做論述。
其實JNDI並不難理解。簡單來說,名稱服務提供了一個把文件,打印機,服務器等實體映射到一個邏輯名稱的機制。例如在操作系統中的名稱服務就把打印機映射到一個I/O端口。而目錄服務可以理解為名稱服務的一個擴展,它答應在服務中的各項擁有自己的屬性。又以打印機為例,打印機可以是彩色打印機,支持雙面打印,支持網絡打印,支持高速打印等。所有這些打印機的屬性都可以儲存在目錄服務中,和相應的打印機聯系起來。一些常見的目錄服務有NIS,NIS+,LDAP和Novell的NDS等。
JNDI使應用程序通過使用邏輯名稱獲取對象和對象提供的服務,從而使程序員可以避免使用與提供對象的機構有關聯的代碼。例如在下面的例子中使用了在JNDI中的數據源,程序員就不需要提供Oracle8i驅動程序的名稱,這樣代碼的移植能力就更強。
下面具體介紹一下數據源和javax.sql.DataSource接口。在數據源中存儲了所有建立數據庫連接的信息。就象通過指定文件名你可以在文件系統中找到文件一樣,通過提供正確的數據源名稱,你可以找到相應的數據庫連接。javax.sql.DataSource接口定義了如何實現數據源。在該接口中定義了九個屬性。表一列出了對這些屬性的描述。由於本文是以Oracle8i為例,在Oracle8i中沒有實現roleName屬性,所以在表中沒有對此屬性做描述。
表一:數據源標准屬性
屬性名稱屬性數據類型描述 databaseName String 數據庫名稱,即數據庫的SID。 dataSourceName String 數據源接口實現類的名稱。 description String 對數據源的描述。 networkProtocol String 和服務器通訊使用的網絡協議名。在Oracle8i中,該屬性只在使用OCI驅動程序時有效,缺省協議是TCP協議。 password String 用戶登錄密碼。 portNumber Int 數據庫服務器使用的端口,缺省值為1521。 serverName String 數據庫服務器名稱。 user String 用戶登錄名。
在javax.sql.DataSource接口中定義了以下方法:
* public synchronized void setDatabaseName(String dbname)
* public synchronized String getDatabaseName()
* public synchronized void setDataSourceName(String dsname)
* public synchronized String getDataSourceName()
* public synchronized void setDescription(String desc)
* public synchronized String getDescription()
* public synchronized void setNetworkProtocol(String np)
* public synchronized String getNetworkProtocol()
* public synchronized void setPassword(String pwd)
* public synchronized void setPortNumber(int pn)
* public synchronized int getPortNumber()
* public synchronized void setServerName(String sn)
* public synchronized String getServerName()
* public synchronized void setUser(String user)
* public synchronized String getUser()
通過這些方法,程序員可以獲得建立連接需要的所有信息。需要注重的是,程序員不可以獲取登陸密碼,這就在一定程度上保證了安全性。需要注重的另一點是所有的方法都是synchronized方法,這是為了保證應用程序的線程安全(Thread-safe)。假如在調用該方法時,即使數據源實例發生變化不會影響程序的正確運行。
除了實現由SUN定義的屬性和方法外,Oracle8i還提供了自己的數據源屬性和方法。這些方法和屬性是在oracle.jdbc.pool.OracleDataSource中實現的。 Oracle8i擴展數據源屬性如表二所示:
表二:
屬性名稱屬性數據類型描述 driverType String 使用的Oracle JDBC驅動程序的類型,包括oci8, thin和kprb url String 數據庫連接的URL。 tnsEntry String TNS條目名稱
在oracle.jdbc.pool.OracleDataSource中除了實現javax.sql.DataSource接口中定義的方法外,還實現了以下方法:
* public synchronized void setDriverType(String dt)
* public synchronized String getDriverType()
* public synchronized void setURL(String url)
* public synchronized String getURL()
* public synchronized void setTNSEntryName(String tns)
* public synchronized String getTNSEntryName()
同時,OracleDataSource還實現了java.io.Serializable和javax.naming.Referenceable接口。進入討論組討論。
獨立使用數據源 實際應用中,你可以把OracleDataSource注冊到JNDI,也可以單獨使用。下面先給出一個單獨使用OracleDataSource的例子:
// 初始化數據源實例
OracleDataSource ods = new OracleDataSource();
ods.setDriverType("thin");
ods.setServerName("Chicago");
ods.setNetworkProtocol("tcp");
ods.setDatabaseName("chidb");
ods.setPortNumber(1521);
ods.setUser("guest");
ods.setPassword("guest");
// 從數據源中獲取數據庫連接
Connection conn = ods.getConnection();
// 通過數據庫連接進行數據操作
………………
使用OracleDataSource時有幾點需要注重:
假如使用的時服務器端內部驅動程序(server-side internal driver),driverType屬性會被設置為kprb,其它所有屬性失效。
假如使用Thin或OCI驅動程序:
URL中可以包括用戶登錄名和用戶登錄密碼。例如:
jdbc:oracle:thin:guest/guest@Chicago:1521:chidb;
果設定了url屬性,tnsEntry, driverType, portNumber, networkProtocol, serverName,和databaseName屬性將失效。
在沒有設定url屬性的情況下,假如設定了tnsEntry屬性,portNumber, networkProtocol, serverName,和databaseName屬性將失效。
假如使用OCI驅動程序,並且networkProtocol屬性被設定為ipc,除user和password外的所有其他屬性將失效。進入討論組討論。
通過JNDI使用數據源 在本節首先給出了一個實際程序,然後通過程序來講解如何通過JNDI查詢數據源。
import java.sql.*;
import javax.sql.*;
import oracle.jdbc.driver.*;
import oracle.jdbc.pool.OracleDataSource;
import javax.naming.*;
import javax.naming.spi.*;
import java.util.Hashtable;
public class DataSourceJNDI
{
public static void main (String args [])
throws SQLException
{
// 初始化名稱服務環境
Context ctx = null;
try
{
Hashtable env = new Hashtable (5);
env.put (Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put (Context.PROVIDER_URL, "file:JNDI");
ctx = new InitialContext(env);
}
catch (NamingException ne)
{
ne.printStackTrace();
}
bind(ctx, "jdbc/chidb");
lookup(ctx, "jdbc/chidb");
}
static void bind (Context ctx, String ln)
throws NamingException, SQLException
{
// 創建一個OracleDataSource實例
OracleDataSource ods = new OracleDataSource();
ods.setDriverType("thin");
ods.setServerName("Chicago");
ods.setNetworkProtocol("tcp");
ods.setDatabaseName("chidb");
ods.setPortNumber(1521);
ods.setUser("guest");
ods.setPassword("guest");
// 把OracleDataSource實例注冊到JNDI中
System.out.println ("Doing a bind with the logical name : " + ln);
ctx.bind (ln,ods);
System.out.println ("SUCcessfully bound");
}
static void lookup (Context ctx, String ln)
throws NamingException, SQLException
{
// 從JNDI中查詢OracleDataSource實例
System.out.println ("Doing a lookup with the logical name : " + ln);
OracleDataSource ods = (OracleDataSource) ctx.lookup (ln);
System.out.println ("Successful lookup");
// 從查詢到的OracleDataSource實例中獲取數據庫連接
Connection conn = ods.getConnection();
// 進行數據庫操作
getUserName(conn);
// 關閉連接
conn.close();
conn = null;
}
static void getUserName(Connection conn)
throws SQLException
{
// 生成一個Statement實例
Statement stmt = conn.createStatement ();
// 從addressbook表中選中姓名列
ResultSet rset = stmt.executeQuery ("select NAME from addressbook");
// 列出addressbook表所有人的姓名
while (rset.next ())
System.out.println ("Name is " + rset.getString (1));
// 關閉RseultSet實例
rset.close();
rset = null;
// 關閉Statement實例
stmt.close();
stmt = null;
}
}
程序首先生成了一個Context實例。javax.naming.Context接口定義了名稱服務環境(Naming Context)及該環境支持的操作。名稱服務環境實際上是由名稱和對象間的相互映射組成。程序中初始化名稱服務環境的環境工廠(Context Factory)是com.sun.jndi.fscontext.RefFSContextFactory(該類在fscontext.jar中可以找到,由於fscontext.jar中包含的不是標准的API,用戶需要從www.javasoft.com中的JNDI專區下載一個名為fscontext1_2beta3.zip的壓縮文件,在該文件中可以找到fscontext.jar)。環境工廠的作用是生成名稱服務環境的實例,javax.naming.spi.InitialContextFactory接口定義了環境工廠應該如何初始化名稱服務環境。在初始化名稱服務環境時還需要定義環境的URL。程序中使用的是"file:JNDI",也就是把環境保存在本地硬盤的JNDI目錄下。
初始化了名稱服務環境後,就可以把數據源實例注冊到名稱服務環境中。注冊時調用javax.naming.Context.bind()方法,參數為注冊名稱和注冊對象。注冊成功後,在JNDI目錄下會生成一個.binding文件,該文件記錄了當前名稱服務環境擁有的名稱及對象。
當需要在名稱服務環境中查詢一個對象時,需要調用javax.naming.Context.lookup()方法,並把查詢到的對象顯式轉化為數據源對象。然後通過該數據源對象進行數據庫操作。
在這個例子中,程序和名稱服務環境都是在同一台計算機上運行。在實際的應用中,程序可以通過RMI或CORBA向名稱服務環境注冊或查詢對象。例如在一個服務器-客戶機結構中,客戶機上的應用程序只需要知道數據源對象在服務器名稱服務環境中的邏輯名稱,就可以通過RMI向服務器查詢數據源,然後通過建立與數據庫的連接.這樣就可以解決本文最開始提出的問題。進入討論組討論。