數(shù)據(jù)源在JDBC中的應(yīng)用
發(fā)表時(shí)間:2024-06-14 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]數(shù)據(jù)源在JDBC中的應(yīng)用(馮!2001年05月17日 18:30)簡介眾所周知,JDBC(Java數(shù)據(jù)庫連接)是Java 2企業(yè)版的重要組成部分。它是基于SQL層的API。通過把SQL語句嵌入JDBC接口的方法中,用戶可以通過Java程序執(zhí)行幾乎所有的數(shù)據(jù)庫操作。JDBC只提供了接口,具體的類的...
數(shù)據(jù)源在JDBC中的應(yīng)用
(馮!2001年05月17日 18:30)
簡介
眾所周知,JDBC(Java數(shù)據(jù)庫連接)是Java 2企業(yè)版的重要組成部分。它是基于SQL層的API。通過把SQL語句嵌入JDBC接口的方法中,用戶可以通過Java程序執(zhí)行幾乎所有的數(shù)據(jù)庫操作。JDBC只提供了接口,具體的類的實(shí)現(xiàn)要求數(shù)據(jù)庫的設(shè)計(jì)者完成。通過生成這些接口的實(shí)例,即使對(duì)于不同的數(shù)據(jù)庫,Java程序也可以正確地執(zhí)行SQL調(diào)用。所以對(duì)于程序員來說,不必把注意力放在如何向數(shù)據(jù)庫發(fā)送SQL指令,因?yàn)槌绦騿T需要了解和用到的只是JDBC的接口,只有在極少數(shù)情況下會(huì)用到面向特定數(shù)據(jù)庫的類,例如程序員希望使用ORACLE的擴(kuò)展API。
在JDBC程序中,首先需要做的是實(shí)現(xiàn)與數(shù)據(jù)庫的連接。在示例程序中,我們使用的是ORACLE8i的JDBC包。連接數(shù)據(jù)庫通常需要實(shí)現(xiàn)以下幾個(gè)步驟:
1. 注冊(cè)數(shù)據(jù)庫驅(qū)動(dòng)程序(driver)?梢酝ㄟ^調(diào)用java.sql.DriverManager類的registerDriver方法顯式注冊(cè)驅(qū)動(dòng)程序,也可以通過加載數(shù)據(jù)庫驅(qū)動(dòng)程序類隱式注冊(cè)驅(qū)動(dòng)程序。例如我們希望向虛擬機(jī)注冊(cè)O(shè)racle8i JDBC驅(qū)動(dòng)程序
// 顯式注冊(cè)
DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
// 隱式注冊(cè)
Class.forName(“oracle.jdbc.driver.OracleDriver”);
關(guān)于虛擬機(jī)如何自動(dòng)注冊(cè)通過類加載器(ClassLoader)加載的數(shù)據(jù)庫驅(qū)動(dòng)程序超過了本文討論的范圍,在此不做詳細(xì)討論。
2. 建立連接。調(diào)用java.sql.DriverManager類的getConnection()方法可以建立與數(shù)據(jù)庫的連接。GetConnection()方法返回一個(gè)Connection對(duì)象。需要注意的是,getConnection()方法會(huì)自動(dòng)從數(shù)據(jù)庫驅(qū)動(dòng)程序注冊(cè)表中選擇一個(gè)最合適的驅(qū)動(dòng)程序。
3. 建立連接后,允許自動(dòng)更新(AutoCommit)。調(diào)用java.sql.Connection接口的serAutoCommit()方法可以設(shè)定當(dāng)程序向數(shù)據(jù)庫發(fā)出一條SQL指令后,數(shù)據(jù)庫是否立即更新。
下面是一個(gè)具體的實(shí)例。在該實(shí)例中,作為getConnection()方法參數(shù)的url使用的是Net8 keyword-value pair格式。當(dāng)然也可以使用普通格式。數(shù)據(jù)庫安裝在名為Chicago的服務(wù)器上,使用的協(xié)議是TCP協(xié)議,使用的端口是1521,數(shù)據(jù)庫的SID是chidb,使用的數(shù)據(jù)庫驅(qū)動(dòng)程序是Oracle JDBC Thin驅(qū)動(dòng)程序。
import java.sql.*;
// 初始化常數(shù)
private static String url =
“jdbc:oracle:thin:@(description=(address=(host=Chicago)” +
“(protocol=tcp)(port=1521))(connect_data=(sid=chidb)))”;
// 也可以設(shè)定url為“jdbc:oracle:thin:@ Chicago:1521:chidb”
private static String username = “guest”;
private static String password = “guest”;
try
{
// 注冊(cè)數(shù)據(jù)庫
Class.forName(“oracle.jdbc.driver.OracleDriver”);
// 建立連接
Connection conn =
DriverManager.getConnection(url, username, password);
// 允許自動(dòng)更新
Conn.setAutoCommit(true);
}
catch(ClassNotFoundException e )
{
e.printStackTrace();
}
catch(SQLException e)
{
e.printStackTrace();
}
從實(shí)際應(yīng)用的角度出發(fā),我們可以看出采取這種方式連接到數(shù)據(jù)庫存在幾個(gè)問題。第一是安全性問題,由于程序代碼中包含用戶名和密碼,其他人如果能得到bytecode,可以通過反編譯工具獲得用戶名和密碼。第二是代碼的可移植性問題。如果希望連接的數(shù)據(jù)庫名稱或用戶名有所更改,程序員需要修改源程序,然后把修改過的程序發(fā)送給用戶。也就是說,軟件無法脫離數(shù)據(jù)庫獨(dú)立存在。這樣不僅會(huì)大大提高軟件的成本,也不利于軟件本身的發(fā)展。還可能出現(xiàn)這樣的情況:在某些情況下,提供數(shù)據(jù)的機(jī)構(gòu)不希望數(shù)據(jù)庫的用戶名和密碼讓編寫程序的程序員知道知道。這樣就提出了一個(gè)問題,如何使Java和數(shù)據(jù)庫之間建立連接時(shí)隱藏一些敏感的信息。
數(shù)據(jù)源(Data Source)及JNDI
數(shù)據(jù)源是在JDBC 2.0中引入的一個(gè)概念。在JDBC 2.0擴(kuò)展包中定義了javax.sql.DataSource接口來描述這個(gè)概念。如果用戶希望建立一個(gè)數(shù)據(jù)庫連接,通過查詢?cè)贘NDI服務(wù)中的數(shù)據(jù)源,可以從數(shù)據(jù)源中獲取相應(yīng)的數(shù)據(jù)庫連接。這樣用戶就只需要提供一個(gè)邏輯名稱(Logic Name),而不是數(shù)據(jù)庫登錄的具體細(xì)節(jié)。
在這里有必要簡單介紹一下JNDI。JNDI的全稱是Java Naming and Directory Interface, 可以理解為Java名稱和目錄服務(wù)接口。JNDI向應(yīng)用程序提供了一個(gè)查詢和使用遠(yuǎn)程服務(wù)的機(jī)制。這些服務(wù)可以是任何企業(yè)服務(wù)。對(duì)于JDBC應(yīng)用程序來說,JNDI提供的是數(shù)據(jù)庫連接服務(wù)。當(dāng)然JNDI也可以向數(shù)據(jù)庫提供其他服務(wù),但是這超出了本文范圍,在此不做論述。
其實(shí)JNDI并不難理解。簡單來說,名稱服務(wù)提供了一個(gè)把文件,打印機(jī),服務(wù)器等實(shí)體映射到一個(gè)邏輯名稱的機(jī)制。例如在操作系統(tǒng)中的名稱服務(wù)就把打印機(jī)映射到一個(gè)I/O端口。而目錄服務(wù)可以理解為名稱服務(wù)的一個(gè)擴(kuò)展,它允許在服務(wù)中的各項(xiàng)擁有自己的屬性。又以打印機(jī)為例,打印機(jī)可以是彩色打印機(jī),支持雙面打印,支持網(wǎng)絡(luò)打印,支持高速打印等。所有這些打印機(jī)的屬性都可以儲(chǔ)存在目錄服務(wù)中,和相應(yīng)的打印機(jī)聯(lián)系起來。一些常見的目錄服務(wù)有NIS,NIS+,LDAP和Novell的NDS等。
JNDI使應(yīng)用程序通過使用邏輯名稱獲取對(duì)象和對(duì)象提供的服務(wù),從而使程序員可以避免使用與提供對(duì)象的機(jī)構(gòu)有關(guān)聯(lián)的代碼。例如在下面的例子中使用了在JNDI中的數(shù)據(jù)源,程序員就不需要提供Oracle8i驅(qū)動(dòng)程序的名稱,這樣代碼的移植能力就更強(qiáng)。
下面詳細(xì)介紹一下數(shù)據(jù)源和javax.sql.DataSource接口。在數(shù)據(jù)源中存儲(chǔ)了所有建立數(shù)據(jù)庫連接的信息。就象通過指定文件名你可以在文件系統(tǒng)中找到文件一樣,通過提供正確的數(shù)據(jù)源名稱,你可以找到相應(yīng)的數(shù)據(jù)庫連接。javax.sql.DataSource接口定義了如何實(shí)現(xiàn)數(shù)據(jù)源。在該接口中定義了九個(gè)屬性。表一列出了對(duì)這些屬性的描述。由于本文是以O(shè)racle8i為例,在Oracle8i中沒有實(shí)現(xiàn)roleName屬性,所以在表中沒有對(duì)此屬性做描述。
表一:數(shù)據(jù)源標(biāo)準(zhǔn)屬性
屬性名稱
屬性數(shù)據(jù)類型 描述
databaseName String 數(shù)據(jù)庫名稱,即數(shù)據(jù)庫的SID。
dataSourceName String 數(shù)據(jù)源接口實(shí)現(xiàn)類的名稱。
description String 對(duì)數(shù)據(jù)源的描述。
networkProtocol String 和服務(wù)器通訊使用的網(wǎng)絡(luò)協(xié)議名。在Oracle8i中,該屬性只在使用OCI驅(qū)動(dòng)程序時(shí)有效,缺省協(xié)議是TCP協(xié)議。
password String 用戶登錄密碼。
portNumber Int 數(shù)據(jù)庫服務(wù)器使用的端口,缺省值為1521。
serverName String 數(shù)據(jù)庫服務(wù)器名稱。
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()
通過這些方法,程序員可以獲得建立連接需要的所有信息。需要注意的是,程序員不可以獲取登陸密碼,這就在一定程度上保證了安全性。需要注意的另一點(diǎn)是所有的方法都是synchronized方法,這是為了保證應(yīng)用程序的線程安全(Thread-safe)。如果在調(diào)用該方法時(shí),即使數(shù)據(jù)源實(shí)例發(fā)生變化不會(huì)影響程序的正確運(yùn)行。
除了實(shí)現(xiàn)由SUN定義的屬性和方法外,Oracle8i還提供了自己的數(shù)據(jù)源屬性和方法。這些方法和屬性是在oracle.jdbc.pool.OracleDataSource中實(shí)現(xiàn)的。 Oracle8i擴(kuò)展數(shù)據(jù)源屬性如表二所示:
表二:
屬性名稱 屬性數(shù)據(jù)類型 描述
driverType String 使用的Oracle JDBC驅(qū)動(dòng)程序的類型,包括oci8, thin和kprb
url String 數(shù)據(jù)庫連接的URL。
tnsEntry String TNS條目名稱
在oracle.jdbc.pool.OracleDataSource中除了實(shí)現(xiàn)javax.sql.DataSource接口中定義的方法外,還實(shí)現(xiàn)了以下方法:
* 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()
同時(shí),OracleDataSource還實(shí)現(xiàn)了java.io.Serializable和javax.naming.Referenceable接口。
獨(dú)立使用數(shù)據(jù)源
實(shí)際應(yīng)用中,你可以把OracleDataSource注冊(cè)到JNDI,也可以單獨(dú)使用。下面先給出一個(gè)單獨(dú)使用OracleDataSource的例子:
// 初始化數(shù)據(jù)源實(shí)例
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");
// 從數(shù)據(jù)源中獲取數(shù)據(jù)庫連接
Connection conn = ods.getConnection();
// 通過數(shù)據(jù)庫連接進(jìn)行數(shù)據(jù)操作
………………
使用OracleDataSource時(shí)有幾點(diǎn)需要注意:
如果使用的時(shí)服務(wù)器端內(nèi)部驅(qū)動(dòng)程序(server-side internal driver),driverType屬性會(huì)被設(shè)置為kprb,其它所有屬性失效。
如果使用Thin或OCI驅(qū)動(dòng)程序:
URL中可以包括用戶登錄名和用戶登錄密碼。例如:
jdbc:oracle:thin:guest/guest@Chicago:1521:chidb;
如果設(shè)定了url屬性,tnsEntry, driverType, portNumber, networkProtocol, serverName,和databaseName屬性將失效。
在沒有設(shè)定url屬性的情況下,如果設(shè)定了tnsEntry屬性,portNumber, networkProtocol, serverName,和databaseName屬性將失效。
如果使用OCI驅(qū)動(dòng)程序,并且networkProtocol屬性被設(shè)定為ipc,除user和password外的所有其他屬性將失效。
通過JNDI使用數(shù)據(jù)源
在本節(jié)首先給出了一個(gè)實(shí)際程序,然后通過程序來講解如何通過JNDI查詢數(shù)據(jù)源。
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
{
// 初始化名稱服務(wù)環(huán)境
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
{
// 創(chuàng)建一個(gè)OracleDataSource實(shí)例
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實(shí)例注冊(cè)到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實(shí)例
System.out.println ("Doing a lookup with the logical name : " + ln);
OracleDataSource ods = (OracleDataSource) ctx.lookup (ln);
System.out.println ("Successful lookup");
// 從查詢到的OracleDataSource實(shí)例中獲取數(shù)據(jù)庫連接
Connection conn = ods.getConnection();
// 進(jìn)行數(shù)據(jù)庫操作
getUserName(conn);
// 關(guān)閉連接
conn.close();
conn = null;
}
static void getUserName(Connection conn)
throws SQLException
{
// 生成一個(gè)Statement實(shí)例
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));
// 關(guān)閉RseultSet實(shí)例
rset.close();
rset = null;
// 關(guān)閉Statement實(shí)例
stmt.close();
stmt = null;
}
}
程序首先生成了一個(gè)Context實(shí)例。javax.naming.Context接口定義了名稱服務(wù)環(huán)境(Naming Context)及該環(huán)境支持的操作。名稱服務(wù)環(huán)境實(shí)際上是由名稱和對(duì)象間的相互映射組成。程序中初始化名稱服務(wù)環(huán)境的環(huán)境工廠(Context Factory)是com.sun.jndi.fscontext.RefFSContextFactory(該類在fscontext.jar中可以找到,由于fscontext.jar中包含的不是標(biāo)準(zhǔn)的API,用戶需要從www.javasoft.com中的JNDI專區(qū)下載一個(gè)名為fscontext1_2beta3.zip的壓縮文件,在該文件中可以找到fscontext.jar)。環(huán)境工廠的作用是生成名稱服務(wù)環(huán)境的實(shí)例,javax.naming.spi.InitialContextFactory接口定義了環(huán)境工廠應(yīng)該如何初始化名稱服務(wù)環(huán)境。在初始化名稱服務(wù)環(huán)境時(shí)還需要定義環(huán)境的URL。程序中使用的是"file:JNDI",也就是把環(huán)境保存在本地硬盤的JNDI目錄下。
初始化了名稱服務(wù)環(huán)境后,就可以把數(shù)據(jù)源實(shí)例注冊(cè)到名稱服務(wù)環(huán)境中。注冊(cè)時(shí)調(diào)用javax.naming.Context.bind()方法,參數(shù)為注冊(cè)名稱和注冊(cè)對(duì)象。注冊(cè)成功后,在JNDI目錄下會(huì)生成一個(gè).binding文件,該文件記錄了當(dāng)前名稱服務(wù)環(huán)境擁有的名稱及對(duì)象。
當(dāng)需要在名稱服務(wù)環(huán)境中查詢一個(gè)對(duì)象時(shí),需要調(diào)用javax.naming.Context.lookup()方法,并把查詢到的對(duì)象顯式轉(zhuǎn)化為數(shù)據(jù)源對(duì)象。然后通過該數(shù)據(jù)源對(duì)象進(jìn)行數(shù)據(jù)庫操作。
在這個(gè)例子中,程序和名稱服務(wù)環(huán)境都是在同一臺(tái)計(jì)算機(jī)上運(yùn)行。在實(shí)際的應(yīng)用中,程序可以通過RMI或CORBA向名稱服務(wù)環(huán)境注冊(cè)或查詢對(duì)象。例如在一個(gè)服務(wù)器-客戶機(jī)結(jié)構(gòu)中,客戶機(jī)上的應(yīng)用程序只需要知道數(shù)據(jù)源對(duì)象在服務(wù)器名稱服務(wù)環(huán)境中的邏輯名稱,就可以通過RMI向服務(wù)器查詢數(shù)據(jù)源,然后通過建立與數(shù)據(jù)庫的連接.這樣就可以解決本文最開始提出的問題。