淺談實際開發(fā)中數(shù)據(jù)源在JDBC中的應(yīng)用
2024-07-21 02:15:05
供稿:網(wǎng)友
 
菜鳥學(xué)堂: 
數(shù)據(jù)源在jdbc中的應(yīng)用眾所周知,jdbc(java數(shù)據(jù)庫連接)是java2企業(yè)版的重要組成部分。它是基于sql層的api。通過把sql語句嵌入jdbc接口的方法中,用戶可以通過java程序執(zhí)行幾乎所有的數(shù)據(jù)庫操作。
jdbc只提供了接口,具體的類的實現(xiàn)要求數(shù)據(jù)庫的設(shè)計者完成。通過生成這些接口的實例,即使對于不同的數(shù)據(jù)庫,java程序也可以正確地執(zhí)行sql調(diào)用。所以對于程序員來說,不必把注意力放在如何向數(shù)據(jù)庫發(fā)送sql指令,因為程序員需要了解和用到的只是jdbc的接口,只有在極少數(shù)情況下會用到面向特定數(shù)據(jù)庫的類,例如程序員希望使用oracle的擴展api。
在jdbc程序中,首先需要做的是實現(xiàn)與數(shù)據(jù)庫的連接。在示例程序中,我們使用的是oracle8i的jdbc包。連接數(shù)據(jù)庫通常需要實現(xiàn)以下幾個步驟:
1、注冊數(shù)據(jù)庫驅(qū)動程序(driver)
可以通過調(diào)用java.sql.drivermanager類的registerdriver方法顯式注冊驅(qū)動程序,也可以通過加載數(shù)據(jù)庫驅(qū)動程序類隱式注冊驅(qū)動程序。例如我們希望向虛擬機注冊oracle8i jdbc驅(qū)動程序顯式注冊:
drivermanager.registerdriver
(neworacle.jdbc.driver.oracledriver());
隱式注冊:
class.forname(“oracle.jdbc.driver.oracledriver”);
關(guān)于虛擬機如何自動注冊通過類加載器(classloader)加載的數(shù)據(jù)庫驅(qū)動程序超過了本文討論的范圍,在此不做詳細討論。
2、建立連接
調(diào)用java.sql.drivermanager類的getconnection()方法可以建立與數(shù)據(jù)庫的連接。getconnection()方法返回一個connection對象。需要注意的是,getconnection()方法會自動從數(shù)據(jù)庫驅(qū)動程序注冊表中選擇一個最合適的驅(qū)動程序。
3、建立連接后
允許自動更新(autocommit)。調(diào)用java.sql.connection接口的serautocommit()方法可以設(shè)定當程序向數(shù)據(jù)庫發(fā)出一條sql指令后,數(shù)據(jù)庫是否立即更新。下面是一個具體的實例。
在該實例中,作為getconnection()方法參數(shù)的url使用的是net8 keyword-value pair格式。當然也可以使用普通格式。數(shù)據(jù)庫安裝在名為chicago的服務(wù)器上,使用的協(xié)議是tcp協(xié)議,使用的端口是1521,數(shù)據(jù)庫的sid是chidb,使用的數(shù)據(jù)庫驅(qū)動程序是oracle jdbc thin驅(qū)動程序。
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
{
// 注冊數(shù)據(jù)庫
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();
}
從實際應(yīng)用的角度出發(fā),我們可以看出采取這種方式連接到數(shù)據(jù)庫存在幾個問題。第一是安全性問題,由于程序代碼中包含用戶名和密碼,其他人如果能得到bytecode,可以通過反編譯工具獲得用戶名和密碼。
第二是代碼的可移植性問題。如果希望連接的數(shù)據(jù)庫名稱或用戶名有所更改,程序員需要修改源程序,然后把修改過的程序發(fā)送給用戶。也就是說,軟件無法脫離數(shù)據(jù)庫獨立存在。這樣不僅會大大提高軟件的成本,也不利于軟件本身的發(fā)展。
還可能出現(xiàn)這樣的情況:在某些情況下,提供數(shù)據(jù)的機構(gòu)不希望數(shù)據(jù)庫的用戶名和密碼讓編寫程序的程序員知道知道。這樣就提出了一個問題,如何使java和數(shù)據(jù)庫之間建立連接時隱藏一些敏感的信息。
數(shù)據(jù)源(data source)及jndi數(shù)據(jù)源是在jdbc 2.0中引入的一個概念。在jdbc 2.0擴展包中定義了javax.sql.datasource接口來描述這個概念。如果用戶希望建立一個數(shù)據(jù)庫連接,通過查詢在jndi服務(wù)中的數(shù)據(jù)源,可以從數(shù)據(jù)源中獲取相應(yīng)的數(shù)據(jù)庫連接。
這樣用戶就只需要提供一個邏輯名稱(logic name),而不是數(shù)據(jù)庫登錄的具體細節(jié)。在這里有必要簡單介紹一下jndi。jndi的全稱是java naming and directory interface, 可以理解為java名稱和目錄服務(wù)接口。
jndi向應(yīng)用程序提供了一個查詢和使用遠程服務(wù)的機制。這些服務(wù)可以是任何企業(yè)服務(wù)。對于jdbc應(yīng)用程序來說,jndi提供的是數(shù)據(jù)庫連接服務(wù)。當然jndi也可以向數(shù)據(jù)庫提供其他服務(wù),但是這超出了本文范圍,在此不做論述。
其實jndi并不難理解。簡單來說,名稱服務(wù)提供了一個把文件,打印機,服務(wù)器等實體映射到一個邏輯名稱的機制。例如在操作系統(tǒng)中的名稱服務(wù)就把打印機映射到一個i/o端口。而目錄服務(wù)可以理解為名稱服務(wù)的一個擴展,它允許在服務(wù)中的各項擁有自己的屬性。
又以打印機為例,打印機可以是彩色打印機,支持雙面打印,支持網(wǎng)絡(luò)打印,支持高速打印等。所有這些打印機的屬性都可以儲存在目錄服務(wù)中,和相應(yīng)的打印機聯(lián)系起來。一些常見的目錄服務(wù)有nis,nis+,ldap和novell的nds等。
jndi使應(yīng)用程序通過使用邏輯名稱獲取對象和對象提供的服務(wù),從而使程序員可以避免使用與提供對象的機構(gòu)有關(guān)聯(lián)的代碼。例如在下面的例子中使用了在jndi中的數(shù)據(jù)源,程序員就不需要提供oracle8i驅(qū)動程序的名稱,這樣代碼的移植能力就更強。
下面詳細介紹一下數(shù)據(jù)源和javax.sql.datasource接口。在數(shù)據(jù)源中存儲了所有建立數(shù)據(jù)庫連接的信息。就象通過指定文件名你可以在文件系統(tǒng)中找到文件一樣,通過提供正確的數(shù)據(jù)源名稱,你可以找到相應(yīng)的數(shù)據(jù)庫連接。
javax.sql.datasource接口定義了如何實現(xiàn)數(shù)據(jù)源。在該接口中定義了九個屬性。同時,oracledatasource還實現(xiàn)了java.io.serializable和javax.naming.referenceable接口。獨立使用數(shù)據(jù)源實際應(yīng)用中,你可以把oracledatasource注冊到j(luò)ndi,也可以單獨使用。
下面先給出一個單獨使用oracledatasource的例子:
// 初始化數(shù)據(jù)源實例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ù)庫連接進行數(shù)據(jù)操作
使用oracledatasource時有幾點需要注意:如果使用的時服務(wù)器端內(nèi)部驅(qū)動程序(server-side internal driver),drivertype屬性會被設(shè)置為kprb,其它所有屬性失效。 如果使用thin或oci驅(qū)動程序: url中可以包括用戶登錄名和用戶登錄密碼。例如:
jdbc:oracle:thin:guest/[email protected]:1521:chidb;
如果設(shè)定了url屬性,tnsentry, drivertype, portnumber, networkprotocol, servername,和databasename屬性將失效。在沒有設(shè)定url屬性的情況下,如果設(shè)定了tnsentry屬性,portnumber, networkprotocol, servername,和databasename屬性將失效。
如果使用oci驅(qū)動程序,并且networkprotocol屬性被設(shè)定為ipc,除user和password外的所有其他屬性將失效。通過jndi使用數(shù)據(jù)源在本節(jié)首先給出了一個實際程序,然后通過程序來講解如何通過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)建一個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實例注冊到j(luò)ndi中
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
實例中獲取數(shù)據(jù)庫連接
connection conn = ods.getconnection();
// 進行數(shù)據(jù)庫操作getusername(conn);
// 關(guān)閉連接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));
// 關(guān)閉rseultset實例rset.close();
rset = null;
// 關(guān)閉statement實例stmt.close();
stmt = null;
}
}
程序首先生成了一個context實例。javax.naming.context接口定義了名稱服務(wù)環(huán)境(naming context)及該環(huán)境支持的操作。名稱服務(wù)環(huán)境實際上是由名稱和對象間的相互映射組成。程序中初始化名稱服務(wù)環(huán)境的環(huán)境工廠(context factory)是com.sun.jndi.fscontext.reffscontextfactory(該類在fscontext.jar中可以找到,由于fscontext.jar中包含的不是標準的api,用戶需要從www.javasoft.com中的jndi專區(qū)下載一個名為fscontext1_2beta3.zip的壓縮文件,在該文件中可以找到fscontext.jar)。
環(huán)境工廠的作用是生成名稱服務(wù)環(huán)境的實例,javax.naming.spi.initialcontextfactory接口定義了環(huán)境工廠應(yīng)該如何初始化名稱服務(wù)環(huán)境。在初始化名稱服務(wù)環(huán)境時還需要定義環(huán)境的url。
程序中使用的是"file:jndi",也就是把環(huán)境保存在本地硬盤的jndi目錄下。初始化了名稱服務(wù)環(huán)境后,就可以把數(shù)據(jù)源實例注冊到名稱服務(wù)環(huán)境中。注冊時調(diào)用javax.naming.context.bind()方法,參數(shù)為注冊名稱和注冊對象。
注冊成功后,在jndi目錄下會生成一個.binding文件,該文件記錄了當前名稱服務(wù)環(huán)境擁有的名稱及對象。當需要在名稱服務(wù)環(huán)境中查詢一個對象時,需要調(diào)用javax.naming.context.lookup()方法,并把查詢到的對象顯式轉(zhuǎn)化為數(shù)據(jù)源對象。
然后通過該數(shù)據(jù)源對象進行數(shù)據(jù)庫操作。在這個例子中,程序和名稱服務(wù)環(huán)境都是在同一臺計算機上運行。在實際的應(yīng)用中,程序可以通過rmi或corba向名稱服務(wù)環(huán)境注冊或查詢對象。
例如在一個服務(wù)器-客戶機結(jié)構(gòu)中,客戶機上的應(yīng)用程序只需要知道數(shù)據(jù)源對象在服務(wù)器名稱服務(wù)環(huán)境中的邏輯名稱,就可以通過rmi向服務(wù)器查詢數(shù)據(jù)源,然后通過建立與數(shù)據(jù)庫的連接.這樣就可以解決本文最開始提出的問題。