到目前為目,jdbc2的連結池只是一個接口,沒有真正的實現,jdbc3正在開發中,據報已經支持連結池,但..........
jdbc3用了jndi技術,連結池的配置可以讓一個高手都煩死.
目前第三方已經實現的連結池當然是poolman,1.0版對一般用戶來說已經足夠用了.配置也簡單,2.0版雖然增加了一些功能,但配置也是采用jndi,對rmi和ejb不懂的朋友可能很煩.建議用1.0的了.
如果有興趣,自己也可以實現連結池,最關鍵的技術也就是把連結作為參數傳給一個bean,用完后返回這個參數連結而不是關閉.
下面是一個簡單的實現:
dbconnectionmanager.java程序清單如下:
001 import java.io.*;
002 import java.sql.*;
003 import java.util.*;
004 import java.util.date;
005
006 /**
007 * 管理類dbconnectionmanager支持對一個或多個由屬性文件定義的數據庫連接
008 * 池的訪問.客戶程序可以調用getinstance()方法訪問本類的唯一實例.
009 */
010 public class dbconnectionmanager {
011 static private dbconnectionmanager instance; // 唯一實例
012 static private int clients;
013
014 private vector drivers = new vector();
015 private printwriter log;
016 private hashtable pools = new hashtable();
017
018 /**
019 * 返回唯一實例.如果是第一次調用此方法,則創建實例
020 *
021 * @return dbconnectionmanager 唯一實例
022 */
023 static synchronized public dbconnectionmanager getinstance() {
024 if (instance == null) {
025 instance = new dbconnectionmanager();
026 }
027 clients++;
028 return instance;
029 }
030
031 /**
032 * 建構函數私有以防止其它對象創建本類實例
033 */
034 private dbconnectionmanager() {
035 init();
036 }
037
038 /**
039 * 將連接對象返回給由名字指定的連接池
040 *
041 * @param name 在屬性文件中定義的連接池名字
042 * @param con 連接對象/
043 */
044 public void freeconnection(string name, connection con) {
045 dbconnectionpool pool = (dbconnectionpool) pools.get(name);
046 if (pool != null) {
047 pool.freeconnection(con);
048 }
049 }
050
051 /**
052 * 獲得一個可用的(空閑的)連接.如果沒有可用連接,且已有連接數小于最大連接數
053 * 限制,則創建并返回新連接
054 *
055 * @param name 在屬性文件中定義的連接池名字
056 * @return connection 可用連接或null
057 */
058 public connection getconnection(string name) {
059 dbconnectionpool pool = (dbconnectionpool) pools.get(name);
060 if (pool != null) {
061 return pool.getconnection();
062 }
063 return null;
064 }
065
066 /**
067 * 獲得一個可用連接.若沒有可用連接,且已有連接數小于最大連接數限制,
068 * 則創建并返回新連接.否則,在指定的時間內等待其它線程釋放連接.
069 *
070 * @param name 連接池名字
071 * @param time 以毫秒計的等待時間/
072 * @return connection 可用連接或null
073 */
074 public connection getconnection(string name, long time) {
075 dbconnectionpool pool = (dbconnectionpool) pools.get(name);
076 if (pool != null) {
077 return pool.getconnection(time);
078 }
079 return null;
080 }
081
082 /**
083 * 關閉所有連接,撤銷驅動程序的注冊/
084 */
085 public synchronized void release() {
086 // 等待直到最后一個客戶程序調用
087 if (--clients != 0) {
088 return;
089 }
090
091 enumeration allpools = pools.elements();
092 while (allpools.hasmoreelements()) {
093 dbconnectionpool pool = (dbconnectionpool) allpools.nextelement();
094 pool.release();
095 }
096 enumeration alldrivers = drivers.elements();
097 while (alldrivers.hasmoreelements()) {
098 driver driver = (driver) alldrivers.nextelement();
099 try {
100 drivermanager.deregisterdriver(driver);
101 log("撤銷jdbc驅動程序 " + driver.getclass().getname()+"的注冊///");
102 }
103 catch (sqlexception e) {
104 log(e, "無法撤銷下列jdbc驅動程序的注冊: " + driver.getclass().getname());
105 }
106 }
107 }
108
109 /**
110 * 根據指定屬性創建連接池實例.
111 *
112 * @param props 連接池屬性
113 */
114 private void createpools(properties props) {
115 enumeration propnames = props.propertynames();
116 while (propnames.hasmoreelements()) {
117 string name = (string) propnames.nextelement();
118 if (name.endswith(".url")) {
119 string poolname = name.substring(0, name.lastindexof("."));
120 string url = props.getproperty(poolname + ".url");
121 if (url == null) {
122 log("沒有為連接池" + poolname + "指定url");
123 continue;
124 }
125 string user = props.getproperty(poolname + ".user");
126 string password = props.getproperty(poolname + ".password");
127 string maxconn = props.getproperty(poolname + ".maxconn", "0");
128 int max;
129 try {
130 max = integer.valueof(maxconn).intvalue();
131 }
132 catch (numberformatexception e) {
133 log("錯誤的最大連接數限制: " + maxconn + " .連接池: " + poolname);
134 max = 0;
135 }
136 dbconnectionpool pool =
137 new dbconnectionpool(poolname, url, user, password, max);
138 pools.put(poolname, pool);
139 log("成功創建連接池" + poolname);
140 }
141 }
142 }
143
144 /**
145 * 讀取屬性完成初始化
146 */
147 private void init() {
148 inputstream is = getclass().getresourceasstream("/db.properties");
149 properties dbprops = new properties();
150 try {
151 dbprops.load(is);
152 }
153 catch (exception e) {
154 system.err.println("不能讀取屬性文件. " +
155 "請確保db.properties在classpath指定的路徑中");
156 return;
157 }
158 string logfile = dbprops.getproperty("logfile", "dbconnectionmanager.log");
159 try {
160 log = new printwriter(new filewriter(logfile, true), true);
161 }
162 catch (ioexception e) {
163 system.err.println("無法打開日志文件: " + logfile);
164 log = new printwriter(system.err);
165 }
166 loaddrivers(dbprops);
167 createpools(dbprops);
168 }
169
170 /**
171 * 裝載和注冊所有jdbc驅動程序/
172 *
173 * @param props 屬性
174 */
175 private void loaddrivers(properties props) {
176 string driverclasses = props.getproperty("drivers");
177 stringtokenizer st = new stringtokenizer(driverclasses);
178 while (st.hasmoreelements()) {
179 string driverclassname = st.nexttoken().trim();
180 try {
181 driver driver = (driver)
182 class.forname(driverclassname).newinstance();
183 drivermanager.registerdriver(driver);
184 drivers.addelement(driver);
185 log("成功注冊jdbc驅動程序///" + driverclassname);
186 }
187 catch (exception e) {
188 log("無法注冊jdbc驅動程序: " +
189 driverclassname + ", 錯誤: " + e);
190 }
191 }
192 }
193
194 /**
195 * 將文本信息寫入日志文件
196 */
197 private void log(string msg) {
198 log.println(new date() + ": " + msg);
199 }
200
201 /**
202 * 將文本信息與異常寫入日志文件
203 */
204 private void log(throwable e, string msg) {
205 log.println(new date() + ": " + msg);
206 e.printstacktrace(log);
207 }
208
209 /**
210 * 此內部類定義了一個連接池.它能夠根據要求創建新連接,直到預定的最/
211 * 大連接數為止.在返回連接給客戶程序之前,它能夠驗證連接的有效性.
212 */
213 class dbconnectionpool {
214 private int checkedout;
215 private vector freeconnections = new vector();
216 private int maxconn;
217 private string name;
218 private string password;
219 private string url;
220 private string user;
221
222 /**
223 * 創建新的連接池
224 *
225 * @param name 連接池名字
226 * @param url 數據庫的jdbc url
227 * @param user 數據庫帳號,或 null
228 * @param password 密碼,或 null
229 * @param maxconn 此連接池允許建立的最大連接數
230 */
231 public dbconnectionpool(string name, string url, string user, string password,
232 int maxconn) {
233 this.name = name;
234 this.url = url;
235 this.user = user;
236 this.password = password;
237 this.maxconn = maxconn;
238 }
239
240 /**
241 * 將不再使用的連接返回給連接池
242 *
243 * @param con 客戶程序釋放的連接
244 */
245 public synchronized void freeconnection(connection con) {
246 // 將指定連接加入到向量末尾
247 freeconnections.addelement(con);
248 checkedout--;
249 notifyall();
250 }
251
252 /**
253 * 從連接池獲得一個可用連接.如沒有空閑的連接且當前連接數小于最大連接
254 * 數限制,則創建新連接.如原來登記為可用的連接不再有效,則從向量刪除之,
255 * 然后遞歸調用自己以嘗試新的可用連接.
256 */
257 public synchronized connection getconnection() {
258 connection con = null;
259 if (freeconnections.size() > 0) {
260 // 獲取向量中第一個可用連接
261 con = (connection) freeconnections.firstelement();
262 freeconnections.removeelementat(0);
263 try {
264 if (con.isclosed()) {
265 log("從連接池" + name+"刪除一個無效連接");
266 // 遞歸調用自己,嘗試再次獲取可用連接
267 con = getconnection();
268 }
269 }
270 catch (sqlexception e) {
271 log("從連接池" + name+"刪除一個無效連接");
272 // 遞歸調用自己,嘗試再次獲取可用連接
273 con = getconnection();
274 }
275 }
276 else if (maxconn == 0 || checkedout < maxconn) {
277 con = newconnection();
278 }
279 if (con != null) {
280 checkedout++;
281 }
282 return con;
283 }
284
285 /**
286 * 從連接池獲取可用連接.可以指定客戶程序能夠等待的最長時間/
287 * 參見前一個getconnection()方法.
288 *
289 * @param timeout 以毫秒計的等待時間限制
290 */
291 public synchronized connection getconnection(long timeout) {
292 long starttime = new date().gettime();
293 connection con;
294 while ((con = getconnection()) == null) {
295 try {
296 wait(timeout);
297 }
298 catch (interruptedexception e) {}
299 if ((new date().gettime() - starttime) >= timeout) {
300 // wait()返回的原因是超時
301 return null;
302 }
303 }
304 return con;
305 }
306
307 /**
308 * 關閉所有連接
309 */
310 public synchronized void release() {
311 enumeration allconnections = freeconnections.elements();
312 while (allconnections.hasmoreelements()) {
313 connection con = (connection) allconnections.nextelement();
314 try {
315 con.close();
316 log("關閉連接池" + name+"中的一個連接");
317 }
318 catch (sqlexception e) {
319 log(e, "無法關閉連接池" + name+"中的連接");
320 }
321 }
322 freeconnections.removeallelements();
323 }
324
325 /**
326 * 創建新的連接
327 */
328 private connection newconnection() {
329 connection con = null;
330 try {
331 if (user == null) {
332 con = drivermanager.getconnection(url);
333 }
334 else {
335 con = drivermanager.getconnection(url, user, password);
336 }
337 log("連接池" + name+"創建一個新的連接");
338 }
339 catch (sqlexception e) {
340 log(e, "無法創建下列url的連接: " + url);
341 return null;
342 }
343 return con;
344 }
345 }
346 }
三、類dbconnectionpool說明/
該類在209至345行實現,它表示指向某個數據庫的連接池。數據庫由jdbc url標識。一個jdbc url由三部分組成:協議標識(總是jdbc),驅動程序標識(如 odbc、idb、oracle等),數據庫標識(其格式依賴于驅動程序)。例如,jdbc:odbc:demo,即是一個指向demo數據庫的jdbc url,而且訪問該數據庫要使用jdbc-odbc驅動程序。每個連接池都有一個供客戶程序使用的名字以及可選的用戶帳號、密碼、最大連接數限制。如果web應用程序所支持的某些數據庫操作可以被所有用戶執行,而其它一些操作應由特別許可的用戶執行,則可以為兩類操作分別定義連接池,兩個連接池使用相同的jdbc url,但使用不同的帳號和密碼。
類dbconnectionpool的建構函數需要上述所有數據作為其參數。如222至238行所示,這些數據被保存為它的實例變量:
如252至283行、285至305行所示, 客戶程序可以使用dbconnectionpool類提供的兩個方法獲取可用連接。兩者的共同之處在于:如連接池中存在可用連接,則直接返回,否則創建新的連接并返回。如果沒有可用連接且已有連接總數等于最大限制數,第一個方法將直接返回null,而第二個方法將等待直到有可用連接為止。
所有的可用連接對象均登記在名為freeconnections的向量(vector)中。如果向量中有多于一個的連接,getconnection()總是選取第一個。同時,由于新的可用連接總是從尾部加入向量,從而使得數據庫連接由于長時間閑置而被關閉的風險減低到最小程度。
第一個getconnection()在返回可用連接給客戶程序之前,調用了isclosed()方法驗證連接仍舊有效。如果該連接被關閉或觸發異常,getconnection()遞歸地調用自己以嘗試獲取另外的可用連接。如果在向量freeconnections中不存在任何可用連接,getconnection()方法檢查是否已經指定最大連接數限制。如已經指定,則檢查當前連接數是否已經到達極限。此處maxconn為0表示沒有限制。如果沒有指定最大連接數限制或當前連接數小于該值,該方法嘗試創建新的連接。如創建成功,則增加已使用連接的計數并返回,否則返回空值。
如325至345行所示,創建新連接由newconnection()方法實現。創建過程與是否已經指定數據庫帳號、密碼有關。
jdbc的drivermanager類提供多個getconnection()方法,這些方法要用到jdbc url與其它一些參數,如用戶帳號和密碼等。drivermanager將使用指定的jdbc url確定適合于目標數據庫的驅動程序及建立連接。
在285至305行實現的第二個getconnection()方法需要一個以毫秒為單位的時間參數,該參數表示客戶程序能夠等待的最長時間。建立連接的具體操作仍舊由第一個getconnection()方法實現。
該方法執行時先將starttime初始化為當前時間。在while循環中嘗試獲得一個連接。如果失敗,則以給定的時間值為參數調用wait()。wait()的返回可能是由于其它線程調用notify()或notifyall(),也可能是由于預定時間已到。為找出wait()返回的真正原因,程序用當前時間減開始時間(starttime),如差值大于預定時間則返回空值,否則再次調用getconnection()。
把空閑的連接登記到連接池由240至250行的freeconnection()方法實現,它的參數為返回給連接池的連接對象。該對象被加入到freeconnections向量的末尾,然后減少已使用連接計數。調用notifyall()是為了通知其它正在等待可用連接的線程。
許多servlet引擎為實現安全關閉提供多種方法。數據庫連接池需要知道該事件以保證所有連接能夠正常關閉。dbconnectionmanager類負協調整個關閉過程,但關閉連接池中所有連接的任務則由dbconnectionpool類負責。在307至323行實現的release()方法供dbconnectionmanager調用。該方法遍歷freeconnections向量并關閉所有連接,然后從向量中刪除這些連接。
四、類dbconnectionmanager 說明/
該類只能創建一個實例,其它對象能夠調用其靜態方法(也稱為類方法)獲得該唯一實例的引用。如031至036行所示,dbconnectionmanager類的建構函數是私有的,這是為了避免其它對象創建該類的實例。
dbconnectionmanager類的客戶程序可以調用getinstance()方法獲得對該類唯一實例的引用。如018至029行所示,類的唯一實例在getinstance()方法第一次被調用期間創建,此后其引用就一直保存在靜態變量instance中。每次調用getinstance()都增加一個dbconnectionmanager的客戶程序計數。即,該計數代表引用dbconnectionmanager唯一實例的客戶程序總數,它將被用于控制連接池的關閉操作。
該類實例的初始化工作由146至168行之間的私有方法init()完成。其中 getresourceasstream()方法用于定位并打開外部文件。外部文件的定位方法依賴于類裝載器的實現。標準的本地類裝載器查找操作總是開始于類文件所在路徑,也能夠搜索classpath中聲明的路徑。db.properties是一個屬性文件,它包含定義連接池的鍵-值對??晒┒x的公用屬性如下:
drivers 以空格分隔的jdbc驅動程序類列表/
logfile 日志文件的絕對路徑
其它的屬性和特定連接池相關,其屬性名字前應加上連接池名字:
< poolname>.url 數據庫的 jdbc url
< poolname>.maxconn 允許建立的最大連接數,0表示沒有限制
< poolname>.user 用于該連接池的數據庫帳號
< poolname>.password 相應的密碼/
其中url屬性是必需的,而其它屬性則是可選的。數據庫帳號和密碼必須合法。用于windows平臺的db.properties文件示例如下:
drivers=sun.jdbc.odbc.jdbcodbcdriver jdbc.idbdriver
logfile=d://user//src//java//dbconnectionmanager//log.txt
idb.url=jdbc:idb:c://local//javawebserver1.1//db//db.prp
idb.maxconn=2
access.url=jdbc:odbc:demo
access.user=demo
access.password=demopw
注意在windows路徑中的反斜杠必須輸入2個,這是由于屬性文件中的反斜杠同時也是一個轉義字符。
init()方法在創建屬性對象并讀取db.properties文件之后,就開始檢查logfile屬性。如果屬性文件中沒有指定日志文件,則默認為當前目錄下的dbconnectionmanager.log文件。如日志文件無法使用,則向system.err輸出日志記錄。
裝載和注冊所有在drivers屬性中指定的jdbc驅動程序由170至192行之間的loaddrivers()方法實現。該方法先用stringtokenizer將drivers屬性值分割為對應于驅動程序名稱的字符串,然后依次裝載這些類并創建其實例,最后在 drivermanager中注冊該實例并把它加入到一個私有的向量drivers。向量drivers將用于關閉服務時從drivermanager取消所有jdbc 驅動程序的注冊。
init()方法的最后一個任務是調用私有方法createpools()創建連接池對象。如109至142行所示,createpools()方法先創建所有屬性名字的枚舉對象(即enumeration對象,該對象可以想象為一個元素系列,逐次調用其nextelement()方法將順序返回各元素),然后在其中搜索名字以“.url”結尾的屬性。對于每一個符合條件的屬性,先提取其連接池名字部分,進而讀取所有屬于該連接池的屬性,最后創建連接池對象并把它保存在實例變量pools中。散列表(hashtable類 )pools實現連接池名字到連接池對象之間的映射,此處以連接池名字為鍵,連接池對象為值。
為便于客戶程序從指定連接池獲得可用連接或將連接返回給連接池,類dbconnectionmanager提供了方法getconnection()和freeconnection()。所有這些方法都要求在參數中指定連接池名字,具體的連接獲取或返回操作則調用對應的連接池對象完成。它們的實現分別在051至064行、066至080行、038至049行。
如082至107行所示,為實現連接池的安全關閉,dbconnectionmanager提供了方法release()。在上面我們已經提到,所有dbconnectionmanager的客戶程序都應該調用靜態方法getinstance()以獲得該管理器的引用,此調用將增加客戶程序計數??蛻舫绦蛟陉P閉時調用release()可以遞減該計數。當最后一個客戶程序調用release(),遞減后的引用計數為0,就可以調用各個連接池的release()方法關閉所有連接了。管理類release()方法最后的任務是撤銷所有jdbc驅動程序的注冊。
五、servlet使用連接池示例
servlet api所定義的servlet生命周期類如:
1) 創建并初始化servlet(init()方法)。
2) 響應客戶程序的服務請求(service()方法)。
3) servlet終止運行,釋放所有資源(destroy()方法)。
本例演示連接池應用,上述關鍵步驟中的相關操作為:
1) 在init(),用實例變量connmgr 保存調用dbconnectionmanager.getinstance()所返回的引用。
2) 在service(),調用getconnection(),執行數據庫操作,用freeconnection()將連接返回給連接池。
3) 在destroy(),調用release()關閉所有連接,釋放所有資源。
示例程序清單如下:
code:
--------------------------------------------------------------------------------
import java.io.*;import java.sql.*;import javax.servlet.*;import javax.servlet.http.*;public class testservlet extends httpservlet { private dbconnectionmanager connmgr; public void init(servletconfig conf) throws servletexception { super.init(conf); connmgr = dbconnectionmanager.getinstance(); } public void service(httpservletrequest req, httpservletresponse res) throws ioexception { res.setcontenttype("text/html"); printwriter out = res.getwriter(); connection con = connmgr.getconnection("idb"); if (con == null) { out.println("不能獲取數據庫連接."); return; } resultset rs = null; resultsetmetadata md = null; statement stmt = null; try { stmt = con.createstatement(); rs = stmt.executequery("select * from employee"); md = rs.getmetadata(); out.println("< h1>職工數據< /h1>"); while (rs.next()) { out.println("< br>"); for (int i = 1; i < md.getcolumncount(); i++) { out.print(rs.getstring(i) + ", "); } } stmt.close(); rs.close(); } catch (sqlexception e) { e.printstacktrace(out); } connmgr.freeconnection("idb", con); } public void destroy() { connmgr.release(); super.destroy(); }}
--------------------------------------------------------------------------------
發表于 @ 2006年03月17日 11:29 pm | 評論 (0)
從數據庫中讀出圖片并顯示的示例代碼
< !-- -- -- -- -- -- -- -- -- -- -- -- -- --servlet-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -->package photo;import javax.servlet.*;import javax.servlet.http.*;import java.io.*;import java.util.*;import java.lang.*;import java.sql.*;/*** <p>title: </p>* <p>description: </p>* <p>copyright: copyright (c) 2002</p>* <p>company: </p>* @author unascribed* @version 1.0*/public class showimage extends httpservlet { private static final string content_type = "image/*"; /** * 定義數據庫連接字符串,jdbc.odbc橋 */ private string driver_class = "oracle.jdbc.driver.oracledriver"; private string connect_string = "jdbc:oracle:thin:xxw/[email protected]:1521:orcl"; connection conn = null; resultset rs = null; statement stmt = null; /******************************************** * 定義應用變量 ******************************************/ private string sqlstring = ""; //定義查詢語句 public string m_eorrmenage = ""; //定義錯誤信息變量 private inputstream in = null; //定義輸入流 private int len = 10 * 1024 * 1024; //定義字符數組長度 //initialize global variables public void init() throws servletexception { /** * 連接數據庫 */ try { class.forname(driver_class); } catch (java.lang.classnotfoundexception e) { //異常 system.err.println("databean():" + e.getmessage()); } } //process the http get request public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { response.setcontenttype(content_type); printwriter out = response.getwriter(); //在數據庫中的照片的id int photoid = 0; /********************************************* * 接受上文傳遞的圖片id號 * 上文傳輸文件名稱為photoid *********************************************/ try { photoid = integer.parseint(request.getparameter("photoid")); sqlstring = "select * from xxw_photo where p_id=" + photoid; } catch (exception e) { e.printstacktrace(); response.setcontenttype("text/html; charset=gb2312"); m_eorrmenage = "請輸入圖片id號"; m_eorrmenage = new string(m_eorrmenage.getbytes("iso8859_1"), "gbk"); out.println("<%@ page contenttype='text/html; charset=gb2312' %>"); out.println("<html>"); out.println("<head><title>id</title></head>"); out.println("<body>"); out.println("<p>" + m_eorrmenage + "</p>"); out.println("</body></html>"); } /***************************************************** * 執行查詢語句 *****************************************************/ try { conn = drivermanager.getconnection(connect_string); stmt = conn.createstatement(); rs = stmt.executequery(sqlstring); } //try catch (sqlexception ex) { system.err.println("aq.executeupdate:" + ex.getmessage()); m_eorrmenage = "對不起,數據庫無法完成此操作!"; m_eorrmenage = new string(m_eorrmenage.getbytes("iso8859_1"), "gbk"); response.setcontenttype("text/html; charset=gb2312"); out.println("<html>"); out.println("<head><title>no_database</title></head>"); out.println("<body>"); out.println("<p>" + m_eorrmenage + "</p>"); out.println("</body></html>"); } /********************************************* * 將圖片流讀入字符數組中,并顯示到客戶端 ********************************************/ try { if (rs.next()) { in = rs.getbinarystream("photo"); response.reset(); //返回在流中被標記過的位置 response.setcontenttype("image/jpg"); //或gif等 // int len=in.available();//得到文件大小 outputstream toclient = response.getoutputstream(); byte[] p_buf = new byte[len]; int i; while ((i = in.read(p_buf)) != -1) { toclient.write(p_buf, 0, i); } in.close(); toclient.flush(); //強制清出緩沖區 toclient.close(); } else { m_eorrmenage = "無此圖片!"; m_eorrmenage = new string(m_eorrmenage.getbytes("iso8859_1"), "gbk"); response.setcontenttype("text/html; charset=gb2312"); out.println("<html>"); out.println( "<head><title>this photo isn't have</title></head>"); out.println("<body>"); out.println("<p>" + m_eorrmenage + "</p>"); out.println("</body></html>"); } rs.close(); } catch (exception e) { e.printstacktrace(); m_eorrmenage = "無法讀取圖片!"; m_eorrmenage = new string(m_eorrmenage.getbytes("iso8859_1"), "gbk"); response.setcontenttype("text/html; charset=gb2312"); out.println("<%@ page contenttype='text/html; charset=gb2312' %>"); out.println("<html>"); out.println("<head><title>no photo</title></head>"); out.println("<body>"); out.println("<p>" + m_eorrmenage + "</p>"); out.println("</body></html>"); } } //clean up resources public void destroy() { try { conn.close(); } catch (sqlexception e) { system.err.println("aq.executeupdate:" + e.getmessage()); m_eorrmenage = "對不起,數據庫無法完成此操作!"; } }}
--------------------------------------------------------------------------------
<!---------------------------顯示---------------------------------------------->
<html>
<head>
<title>untitled document</title>
</head>
<body bgcolor="#ffffff" text="#000000">
<table>
<%
int i=1;
while(i<3){
%>
<tr>
<td colspan="3"> <img border="1" src="http://192.168.1.50:8100/showimage?photoid=<%=i%>"></td>
</tr>
<%
i++;
}
%>
</table>
</body>
</html>
注:此程序對于從數據庫讀取圖片后寫入文件請參考代碼者留意
發表于 @ 2006年03月17日 11:26 pm | 評論 (0)
消除jdbc的瓶頸
摘要
大部分的j2ee(java 2 platform, enterprise edition)和其它類型的java應用都需要與數據庫進行交互。與數據庫進行交互需要反復地調用sql語句、連接管理、事務生命周期、結果處理和異常處理。這些操作都是很常見的;不過這個重復的使用并不是必定需要的。在這篇文章中,我們將介紹一個靈活的架構,它可以解決與一個兼容jdbc的數據庫的重復交互問題。
最近在為公司開發一個小的j2ee應用時,我對執行和處理sql調用的過程感到很麻煩。我認為在java開發者中一定有人已經開發了一個架構來消除這個流程。不過,搜索諸如/"java sql framework" 或者 "jdbc [java database connectivity] framework"等都沒有得到滿意的結果。
問題的提出?
在講述一個解決方法之前,我們先將問題描述一下。如果你要通過一個jdbc數據源執行sql指令時,你通常需要做些什么呢?
1、建立一個sql字符串
2、得到一個連接
3、得到一個預處理語句(prepared statement)
4、將值組合到預處理語句中
5、執行語句
6、遍歷結果集并且形成結果對象
還有,你必須考慮那些不斷產生的sqlexceptions;如果這些步驟出現不同的地方,sqlexecptions的開銷就會復合在一起,因為你必須使用多個try/catch塊。
不過,如果我們仔細地觀察一下這些步驟,就可以發現這個過程中有幾個部分在執行期間是不變的:你通常都使用同一個方式來得到一個連接和一個預處理語句。組合預處理語句的方式通常也是一樣的,而執行和處理查詢則是特定的。你可以在六個步驟中提取中其中三個。即使在有點不同的步驟中,我們也可以在其中提取出公共的功能。但是我們應該怎樣自動化及簡化這個過程呢?
查詢架構
我們首先定義一些方法的簽名,這些方法是我們將要用來執行一個sql語句的。要注意讓它保持簡單,只傳送需要的變量,我們可以編寫一些類似下面簽名的方法:
code:
--------------------------------------------------------------------------------
public object[] executequery(string sql, object[] pstmntvalues,resultprocessor processor);
--------------------------------------------------------------------------------
我們知道在執行期間有所不同的方面是sql語句、預處理語句的值和結果集是如何分析的。很明顯,sql參數指的是sql語句。pstmntvalues對象數據包含有必須插入到預處理語句中的值,而processor參數則是處理結果集并且返回結果對象的一個對象;我將在后面更詳細地討論這個對象。
在這樣一個方法簽名中,我們就已經將每個jdbc數據庫交互中三個不變的部分隔離開來?,F在讓我們討論exeutequery()及其它支持的方法,它們都是sqlprocessor類的一部分:
code:
--------------------------------------------------------------------------------
public class sqlprocessor {public object[] executequery(string sql, object[] pstmntvalues,resultprocessor processor) {//get a connection (assume it's part of a connectionmanager class)connection conn = connectionmanager.getconnection();//hand off our connection to the method that will actually execute//the callobject[] results = handlequery(sql, pstmntvalues, processor, conn);//close the connectioncloseconn(conn);//and return its resultsreturn results;}protected object[] handlequery(string sql, object[] pstmntvalues,resultprocessor processor, connection conn) {//get a prepared statement to usepreparedstatement stmnt = null;try {//get an actual prepared statementstmnt = conn.preparestatement(sql);//attempt to stuff this statement with the given values. if//no values were given, then we can skip this step.if(pstmntvalues != null) {preparedstatementfactory.buildstatement(stmnt, pstmntvalues);}//attempt to execute the statementresultset rs = stmnt.executequery();//get the results from this queryobject[] results = processor.process(rs);//close out the statement only. the connection will be closed by the//caller.closestmnt(stmnt);//return the resultsreturn results;//any sql exceptions that occur should be recast to our runtime query//exception and thrown from here} catch(sqlexception e) {string message = "could not perform the query for " + sql;//close out all resources on an exceptioncloseconn(conn);closestmnt(stmnt);//and rethrow as our runtime exceptionthrow new databasequeryexception(message);}}}...}
--------------------------------------------------------------------------------
在這些方法中,有兩個部分是不清楚的:preparedstatementfactory.buildstatement() 和 handlequery()'s processor.process()方法調用。buildstatement()只是將參數對象數組中的每個對象放入到預處理語句中的相應位置。例如:
code:
--------------------------------------------------------------------------------
...//loop through all objects of the values array, and set the value//of the prepared statement using the value array indexfor(int i = 0; i < values.length; i++) {//if the object is our representation of a null value, then handle it separatelyif(value instanceof nullsqltype) {stmnt.setnull(i + 1, ((nullsqltype) value).getfieldtype());} else {stmnt.setobject(i + 1, value);}}
--------------------------------------------------------------------------------
由于stmnt.setobject(int index, object value)方法不可以接受一個null對象值,因此我們必須使用自己特殊的構造:nullsqltype類。nullsqltype表示一個null語句的占位符,并且包含有該字段的jdbc類型。當一個nullsqltype對象實例化時,它獲得它將要代替的字段的sql類型。如上所示,當預處理語句通過一個nullsqltype組合時,你可以使用nullsqltype的字段類型來告訴預處理語句該字段的jdbc類型。這就是說,你使用nullsqltype來表明正在使用一個null值來組合一個預處理語句,并且通過它存放該字段的jdbc類型。
現在我已經解釋了preparedstatementfactory.buildstatement()的邏輯,我將解釋另一個缺少的部分:processor.process()。processor是resultprocessor類型,這是一個接口,它表示由查詢結果集建立域對象的類。resultprocessor包含有一個簡單的方法,它返回結果對象的一個數組:
code:
--------------------------------------------------------------------------------
public interface resultprocessor {public object[] process(resultset rs) throws sqlexception;}
--------------------------------------------------------------------------------
一個典型的結果處理器遍歷給出的結果集,并且由結果集合的行中形成域對象/對象結構。現在我將通過一個現實世界中的例子來綜合講述一下。
查詢例子
你經常都需要利用一個用戶的信息表由數據庫中得到一個用戶的對象,假設我們使用以下的users表:
code:
--------------------------------------------------------------------------------
users tablecolumn name data type id number username varchar f_name varchar l_name varchar email varchar
--------------------------------------------------------------------------------
并且假設我們擁有一個user對象,它的構造器是:
public user(int id, string username, string firstname,
string lastname, string email)
如果我們沒有使用這篇文章講述的架構,我們將需要一個頗大的方法來處理由數據庫中接收用戶信息并且形成user對象。那么我們應該怎樣利用我們的架構呢?
首先,我們構造sql語句:
code:
--------------------------------------------------------------------------------
private static final string sql_get_user = "select * from users where id = ?";
--------------------------------------------------------------------------------
接著,我們形成resultprocessor,我們將使用它來接受結果集并且形成一個user對象:
code:
--------------------------------------------------------------------------------
public class userresultprocessor implements resultprocessor {//column definitions here (i.e., column_username, etc...)..public object[] process(resultset rs) throws sqlexception {//where we will collect all returned userslist users = new arraylist();user user = null;//if there were results returned, then process themwhile(rs.next()) {user = new user(rs.getint(column_id), rs.getstring(column_username),rs.getstring(column_first_name), rs.getstring(column_last_name),rs.getstring(column_email));users.add(user);}return users.toarray(new user[users.size()]);
--------------------------------------------------------------------------------
最后,我們將寫一個方法來執行查詢并且返回user對象:
code:
--------------------------------------------------------------------------------
public user getuser(int userid) {//get a sql processor and execute the querysqlprocessor processor = new sqlprocessor();object[] users = processor.executequery(sql_get_user_by_id,new object[] {new integer(userid)},new userresultprocessor());//and just return the first user objectreturn (user) users[0];}
--------------------------------------------------------------------------------
這就是全部。我們只需要一個處理類和一個簡單的方法,我們就可以無需進行直接的連接維護、語句和異常處理。此外,如果我們擁有另外一個查詢由用戶表中得到一行,例如通過用戶名或者密碼,我們可以重新使用userresultprocessor。我們只需要插入一個不同的sql語句,并且可以重新使用以前方法的用戶處理器。由于返回行的元數據并不依賴查詢,所以我們可以重新使用結果處理器。
更新的架構
那么數據庫更新又如何呢?我們可以用類似的方法處理,只需要進行一些修改就可以了。首先,我們必須增加兩個新的方法到sqlprocessor類。它們類似executequery()和handlequery()方法,除了你無需處理結果集,你只需要將更新的行數作為調用的結果:
code:
--------------------------------------------------------------------------------
public void executeupdate(string sql, object[] pstmntvalues,updateprocessor processor) {//get a connectionconnection conn = connectionmanager.getconnection();//send it off to be executedhandleupdate(sql, pstmntvalues, processor, conn);//close the connectioncloseconn(conn);}protected void handleupdate(string sql, object[] pstmntvalues,updateprocessor processor, connection conn) {//get a prepared statement to usepreparedstatement stmnt = null;try {//get an actual prepared statementstmnt = conn.preparestatement(sql);//attempt to stuff this statement with the given values. if//no values were given, then we can skip this step.if(pstmntvalues != null) {preparedstatementfactory.buildstatement(stmnt, pstmntvalues);}//attempt to execute the statementint rows = stmnt.executeupdate();//now hand off the number of rows updated to the processorprocessor.process(rows);//close out the statement only. the connection will be closed by the//caller.closestmnt(stmnt);//any sql exceptions that occur should be recast to our runtime query//exception and thrown from here} catch(sqlexception e) {string message = "could not perform the update for " + sql;//close out all resources on an exceptioncloseconn(conn);closestmnt(stmnt);//and rethrow as our exceptionthrow new databaseupdateexception(message);}}
--------------------------------------------------------------------------------
這些方法和查詢處理方法的區別僅在于它們是如何處理調用的結果:由于一個更新的操作只返回更新的行數,因此我們無需結果處理器。我們也可以忽略更新的行數,不過有時我們可能需要確認一個更新的產生。updateprocessor獲得更新行的數據,并且可以對行的數目進行任何類型的確認或者記錄:
code:
--------------------------------------------------------------------------------
public interface updateprocessor {public void process(int rows);}
--------------------------------------------------------------------------------
如果一個更新的調用必須至少更新一行,這樣實現updateprocessor的對象可以檢查更新的行數,并且可以在沒有行被更新的時候拋出一個特定的異常?;蛘?,我們可能需要記錄下更新的行數,初始化一個結果處理或者觸發一個更新的事件。你可以將這些需求的代碼放在你定義的updateprocessor中。你應該知道:各種可能的處理都是存在的,并沒有任何的限制,可以很容易得集成到架構中。
更新的例子
我將繼續使用上面解釋的user模型來講述如何更新一個用戶的信息:
首先,構造sql語句:
code:
--------------------------------------------------------------------------------
private static final string sql_update_user = "update users set username = ?, " +"f_name = ?, " +"l_name = ?, " +"email = ? " +"where id = ?";
--------------------------------------------------------------------------------
接著,構造updateprocessor,我們將用它來檢驗更新的行數,并且在沒有行被更新的時候拋出一個異常:
code:
--------------------------------------------------------------------------------
public class mandatoryupdateprocessor implements updateprocessor {public void process(int rows) {if(rows < 1) {string message = "there were no rows updated as a result of this operation.";throw new illegalstateexception(message);}}}
--------------------------------------------------------------------------------
最后就寫編寫執行更新的方法:
code:
--------------------------------------------------------------------------------
public static void updateuser(user user) {sqlprocessor sqlproce, ssor = new sqlprocessor();//use our get user sql statementsqlprocessor.executeupdate(sql_update_user,new object[] {user.getusername(),user.getfirstname(),user.getlastname(),user.getemail(),new integer(user.getid())},new mandatoryupdateprocessor());
--------------------------------------------------------------------------------
如前面的例子一樣,我們無需直接處理sqlexceptions和connections就執行了一個更新的操作。
事務
前面已經說過,我對其它的sql架構實現都不滿意,因為它們并不擁有預定義語句、獨立的結果集處理或者可處理事務。我們已經通過buildstatement() 的方法解決了預處理語句的問題,還有不同的處理器(processors)已經將結果集的處理分離出來。不過還有一個問題,我們的架構如何處理事務呢?
一個事務和一個獨立sql調用的區別只是在于在它的生命周期內,它都使用同一個連接,還有,自動提交標志也必須設置為off。因為我們必須有一個方法來指定一個事務已經開始,并且在何時結束。在整個事務的周期內,它都使用同一個連接,并且在事務結束的時候進行提交。
要處理事務,我們可以重用sqlprocessor的很多方面。為什么將該類的executeupdate() 和handleupdate()獨立開來呢,將它們結合為一個方法也很簡單的。我這樣做是為了將真正的sql執行和連接管理獨立開來。在建立事務系統時,我們必須在幾個sql執行期間對連接進行控制,這樣做就方便多了。
為了令事務工作,我們必須保持狀態,特別是連接的狀態。直到現在,sqlprocessor還是一個無狀態的類。它缺乏成員變量。為了重用sqlprocessor,我們創建了一個事務封裝類,它接收一個sqlprocessor并且透明地處理事務的生命周期。
具體的代碼是:
code:
--------------------------------------------------------------------------------
public class sqltransaction {private sqlprocessor sqlprocessor;private connection conn;//assume constructor that initializes the connection and sets auto commit to false...public void executeupdate(string sql, object[] pstmntvalues,updateprocessor processor) {//try and get the results. if an update fails, then rollback//the transaction and rethrow the exception.try {sqlprocessor.handleupdate(sql, pstmntvalues, processor, conn);} catch(databaseupdateexception e) {rollbacktransaction();throw e;} }public void committransaction() {//try to commit and release all resourcestry {conn.commit();sqlprocessor.closeconn(conn);//if something happens, then attempt a rollback and release resources} catch(exception e) {rollbacktransaction();throw new databaseupdateexception("could not commit the current transaction.");}}private void rollbacktransaction() {//try to rollback and release all resourcestry {conn.rollback();conn.setautocommit(true);sqlprocessor.closeconn(conn);//if something happens, then just swallow it} catch(sqlexception e) {sqlprocessor.closeconn(conn);}}}
--------------------------------------------------------------------------------
sqltransaction擁有許多新的方法,但是其中的大部分都是很簡單的,并且只處理連接或者事務處理。在整個事務周期內,這個事務封裝類只是在sqlprocessor中增加了一個簡單的連接管理。當一個事務開始時,它接收一個新的連接,并且將其自動提交屬性設置為false。其后的每個執行都是使用同一個連接(傳送到sqlprocessor的handleupdate()方法中),因此事務保持完整。
只有當我們的持久性對象或者方法調用committransaction()時,事務才被提交,并且關閉連接。如果在執行期間發生了異常,sqltransaction可以捕捉該異常,自動進行回滾,并且拋出異常。
事務例子
讓我們來看一個簡單的事務
code:
--------------------------------------------------------------------------------
//reuse the sql_update_user statement defined abovepublic static void updateusers(user[] users) {//get our transactionsqltransaction trans = sqlprocessor.starttransaction();//for each user, update ituser user = null;for(int i = 0; i < users.length; i++) {user = users[i];trans.executeupdate(sql_update_user,new object[] {user.getusername(),user.getfirstname(),user.getlastname(),user.getemail(),new integer(user.getid())},new mandatoryupdateprocessor());}//now commit the transactiontrans.committransaction();}
--------------------------------------------------------------------------------
上面為我們展示了一個事務處理的例子,雖然簡單,但我們可以看出它是如何工作的。如果在執行executeupdate()方法調用時失敗,這時將會回滾事務,并且拋出一個異常。調用這個方法的開發者從不需要擔心事務的回滾或者連接是否已經關閉。這些都是在后臺處理的。開發者只需要關心商業的邏輯。
事務也可以很輕松地處理一個查詢,不過這里我沒有提及,因為事務通常都是由一系列的更新組成的。
問題
在我寫這篇文章的時候,對于這個架構,我提出了一些疑問。這里我將這些問題提出來,因為你們可能也會碰到同樣的問題。
自定義連接
如果每個事務使用的連接不一樣時會如何?如果connectionmanager需要一些變量來告訴它從哪個連接池得到連接?你可以很容易就將這些特性集合到這個架構中。executequery() 和 executeupdate()方法(屬于sqlprocessor和sqltransaction類)將需要接收這些自定義的連接參數,并且將他們傳送到connectionmanager。要記得所有的連接管理都將在執行的方法中發生。
此外,如果更面向對象化一點,連接制造者可以在初始化時傳送到sqlprocessor中。然后,對于每個不同的連接制造者類型,你將需要一個sqlprocessor實例。根據你連接的可變性,這或許不是理想的做法。
resultprocessor返回類型
為什么resultprocessor接口指定了process()方法應該返回一個對象的數組?為什么不使用一個list?在我使用這個架構來開發的大部分應用中,sql查詢只返回一個對象。如果構造一個list,然后將一個對象加入其中,這樣的開銷較大,而返回一個對象的一個數組是比較簡單的。不過,如果在你的應用中需要使用對象collections,那么返回一個list更好。
sqlprocessor初始管理
在這篇文章的例子中,對于必須執行一個sql調用的每個方法,初始化一個sqlprocessor。由于sqlprocessors完全是沒有狀態的,所以在調用的方法中將processor獨立出來是很有意義的。
而對于sqltransaction類,則是缺少狀態的,因此它不能獨立使用。我建議你為sqlprocessor類增加一個簡單的方法,而不是學習如何初始化一個sqltransaction,如下所示:
public sqltransaction starttransaction() {
return new sqltransaction(this);
}
這樣就會令全部的事務功能都在sqlprocessor類中訪問到,并且限制了你必須知道的方法調用。
數據庫異常
我使用了幾種不同類型的數據庫異常將全部可能在運行時發生的sqlexceptions封裝起來。在我使用該架構的應用中,我發現將這些異常變成runtime exceptions更為方便,所以我使用了一個異常處理器。你可能認為這些異常應該聲明,這樣它們可以盡量在錯誤的發生點被處理。不過,這樣就會令sql異常處理的流程和以前的sqlexceptions一樣,這種情況我們是盡量避免的。
省心的jdbc programming
這篇文章提出的架構可以令查詢、更新和事務執行的操作更加簡單。在類似的sql調用中,你只需要關注可重用的支持類中的一個方法。我的希望是該架構可以提高你進行jdbc編程的效率。
發表于 @ 2006年03月17日 11:08 pm | 評論 (0)
jdbc初級應用實例(二)[動態訪問數據庫]
上面有一位朋友問了,如果在已經連結的情況下,知道當前連結的庫的表的情況呢?
其實只你已經連結了,你就能知道這個庫中所以情況而不僅僅上表的情況:
有時(我到目前只見到過一次),我們對一種新的數據庫根本不知道它的結構或者是
其中的內容,好壞么我們如何來獲取數據庫的情況呢?
真實的例子是這樣的,我的朋友的公司接到了一個單子,對方使用的數據庫是叫什么
/"titanium/"的,說實話由于本人的孤陋寡聞,在此之前從來不知道還有這種數據庫,更別說如何
訪問了,現在朋友要看里面有什么/"東西/",當然是一籌莫展.所以只好找我.
接到電話后,我先問他是什么平臺上跑的,如果連結的,他說是在windows下可以建立
odbc數據源,哈哈,就是說可以用java建立connection了,ok
只能建立一下connection,那么就可以得到這個數據庫的所有元信息:
databasemetadata dbmd = conn.getmetadata();然后你可以從這個對象獲取以下信
息:
geturl(); //返回與這個數據庫的連結的url,當然是已知的,要不你怎么連上去
getusername(); //返回與這個數據庫的連結的用戶,同上
isreadonly();數據庫是否為只讀
getdatabaseproducename();//數據庫產品名稱
getdatabaseproduceversion();//版本號
getdrivername();//驅動程序
getdriverversion();//驅動程序版本
以上內容沒有什么意義
resultset gettables(string catalog,
string schemapattern,
string tablenamepattern,
string[] types)
可以得到該庫中/"表/"的所有情況,這里的表包括表,視圖,系統表,臨時空間,別名,同義詞
對于各參數:
string catalog,表的目錄,可能為null,/"null/"匹配所有
string schemapattern,表的大綱,同上
string tablenamepattern,表名,同上
string[] types,表的類型,/"null/"匹配所有,可用的類型為:
table,view,sysem table,global temporary,local temporary,alias,synonym
例如:
databasemetadata dbmd = conn.getmetadata();
resultset rs = dbmd.gettables(null,null,null,null);
resultsetmetadata rsmd = rs.getmetadata();
int j = rsmd.getcolumncount();
for(int i=1;i<=j;i++){
out.print(rsmd.getcolumnlabel(i)+/"http://t/");
}
out.println();
while(rs.next()){
for(int i=1;i<=j;i++){
out.print(rs.getstring(i)+/"http://t/");
}
out.println();
}
對于更詳細的表中的列的信息,可以用dbmd(不是rsmd).getcolumns(
string catalog,
string schemapattern,
string tablenamepattern,
string columnnamepattern
)
不僅可以獲得rsmd中的信息,還可以獲得列的大小,小數位數,精度,缺省值,列在表中
的位置等相關信息.
還有兩個方法,調用和獲取表信息一樣,可以獲得存儲過程和索引的信息:
resultset getprocedures(
string catalog,
string schemapattern,
string procedurepattern
);
resultset getindexinfo(
string catalog,
string schemapattern,
string table,
boolean unique,boolean approximate
);
發表于 @ 2006年03月17日 11:06 pm | 評論 (0)
jdbc初級應用實例(一)
jdbc初級應用實例(一)
在了解jdbc基礎知識以后,我們先來寫一個數據庫操作的類(bean)以后我們會
在這個類的基礎上,隨著介紹的深入不斷提供優化的方案.
要把一個數據庫操作獨立到一個類(bean)中,至少要考慮以下幾個方面:
1.對于不同層次的應用,應該有不同的得到連結的方法,如果得到連結的方法要隨
著應用層次的不同而改變,我們就應該把他獨立成一個專門的類中,而把在任何應用層次
中都通用的處理方法封裝到一個(類)bean中.
2.既然考慮到既作為javabean使用又可以用為一個普通類調用,要考慮到javabean
的規范和普通類的靈活性.
3.對于特定的數據庫操作不應封裝到共性的(類)bean中,而應該成為它的擴展類.
以上幾點是充分考慮java的面象對象的思想,經過深入的抽象形成的層次,下面我
們就按這個思想來設計:
一:定義一個用于連結的bean,以后如果要在不同的應用中,如可以在j2ee中從
datasource中得到連結,或從普通的連結池中得到連結,以及直接從drivermanager中得到
連結,只需修改本類中的得到連結的實現方法.
package com.imnamg.axman.beans;
import java.sql.*;
import ..................
public class connectionfactory{
protected connection conn;
connectionfactory() throws sqlexception
{ //構造方法中生成連結
//無論是從datasource還是直接從drivermanager中取得連結.
//先初始化環境,然后取得連結,本例作為初級應用,從
//drivermanager中取得連結,因為是封裝類,所以要把異常拋
//給調用它的程序處理而不要用try{}catch(){}塊自選處理了.
//因為要給業務方法的類繼承,而又不能給調用都訪問,所以
//conn聲明為protected
conn = drivermanager.getconnection(url,user,passwd);
}
/**
在多線程編程中,很多時候有可能在多個線程體中得到同一連
結的引用,但如果在一個線程中關閉了連結,則另一個得到相同
引用的線程就無法操作了,所以我們應該加一個重新建立連結
的輔助方法,有人問為什么既然有這個輔助方法不直接調用這個
輔助而要在構造方法中生成連結?因為這樣可以增加效率,如果
在構造時不能生成連結則就不能生成這個對象了,沒有必要在
對象生成后再測試能不能生成連結.
*/
public void makeconnection(){
//此處的代碼同構造方法,無論以后如果實現連結,都將構造方
//法的代碼復制到此處.
conn = drivermanager.getconnection(url,user,passwd);
}
}
這個類就封裝到這里,當然你可以在這兒增加業務方法,但如果要修改連結的實現,
整個類都要重新編譯,因為業務方法和應用層次無關,代碼一經生成不易變動,所以獨立封裝.
以下我們實現業務方法:
package com.imnamg.axman.beans;
import java.sql.*;
import ..................
public class dboperater extends connectionfactory{
//private statement stmt;
//private resultset rs;
//為什么要注釋成員變量stmt和rs,基礎部分已經說過,如果聲明為成員變量,
//在關閉conn時可以顯示地先關閉rs和stmt,別的沒有任何好處,而顯示關
//閉只是說明你編程風格好,但綜合考慮,我們要生成多個stmt或不是類型的
//stmt就不能聲明為成員方法,否則引用同一對象,所以我們要業務方法中生
//成stmt對象.不僅可以同時處理多個結果集,還可以提高性能和靈活性.
public resultset executequery(string sql) throws sqlexception{
if(conn==null || conn.isclosed())
makeconnection();
statement stmt = con.createstatement(
resultset.type_scroll_insensitive,
resultset.concur_read_only);
//對于一般的查詢操作,我們只要生成一個可流動的結果集就行了.
//而對于在查詢時要更新記錄,我們用另一個業務方法來處理,這樣,
//這樣可以在普通查詢時節省回滾空間.
resultset rs = stmt.executequery(sql);
return rs;
}
public resultset executeupdatabledquery(string sql) throws sqlexception{
if (con == null || con.isclosed())
makeconnection();
statement stmt = con.createstatement(
resultset.type_scroll_insensitive,
resultset.concur_updatabled);
//可更新的結果結要更大的回滾空間,普通查詢時不要調用這個方法
resultset rs = stmt.executequery(sql);
return rs;
}
/**
基于同上的原因,在執行更新操作是我們根本不要任何回滾空間,所以建立
一個基本類型的stmt,實現如下
*/
public int executeupdate(string sql) throws sqlexception{
if (con == null || con.isclosed())
makeconnection();
statement stmt = con.createstatement();
//這個stmt在執行更新操作時更加節省內存,永遠記住,能節省的時候要節省
//每一個字節的內存,雖然硬件設備可能會有很大的物理內存,但內存是給用
//戶用的而不是給程序員用的(!!!!!!!!!!!!!!!!!!)
int s = stmt.executeupdate(sql);
return s;
}
//以上實現了常用功能,還有兩個通用的功能也是/"共性/"的,我們一起在這個封裝類
//中實現:
public preparedstatement getpreparedstmt(string sql) throws sqlexception{
if (con == null || con.isclosed())
makeconnection();
preparedstatement ps = con.preparestatement(sql);
return ps;
}
public callablestatement getcallablestmt(string sql) throws sqlexception{
if (con == null || con.isclosed())
makeconnection();
preparedstatement ps = con.preparecall(sql);
return ps;
}
//記住:對于封裝類而言預編譯語句和存儲過程調用應該從連結中返preparedstatement
//和callablestatement供調用者處理而不是返回它們的處理結果.也就是說封裝類只封
//裝了它們的連結過程.最后再次聲明,一定要有一個close()方法供調用者調用,而且告
//訴調用者無論如果要調用這個方法:
public void close() throws sqlexception{
if(conn != null && !conn.isclosed())
conn.close();
}
//這個方法最好放在connectionfactory中,這樣可以直接調用來只測試連結.而不用再
調用子類來關閉
}
ok,我們已經實現了數據庫常用操作的封裝,注意這些業務方法都是把異常拋給調用者而沒有用
try...catch來處理,你如果在這里處理了那么調用者則無法調試了.對于特定的數據庫的特殊操作,不要封
裝到此類中,可以再從這個類繼承,或直接從connectionfactory類繼承,當然最好是從這個業務類中繼承,
這樣不僅可以調用特殊方法也可以調用共性的業務方法,興一個例子,我在應用oracle時要把xml文件直接
存到數據數和把數據直接讀取為xml文件,那么這兩個方法只對oracle才用到,所以:
package com.inmsg.axman.beans;
import java.sql.*;
import oracle.xml.sql.query.oraclexmlquery;
import oracle.xml.sql.dml.oraclexmlsave;
public class oracledboperater extends dboperater{
public oraclexmlquery getoxquery(string sql,string table) throws exception
{
oraclexmlquery qry = new oraclexmlquery(con,sql);
qry.setrowsettag(table);
qry.setrowtag(/"record/");
return qry;
}
public int insertxml(string path,string table) throws exception
{
oraclexmlsave sav = new oraclexmlsave(con,table);
url url = sav.createurl(path);
sav.setrowtag(/"record/");
int x = sav.insertxml(url);
sav.close();
return x;
}
}
現在,有了這樣的幾個/"東西/"在手里,你還有什么覺得不方便的呢?
雖然本處作為初級應用,但設計思想已經是java高手的套路了,是不是有些自吹自擂了啊?
好的,休息一下吧.
發表于 @ 2006年03月17日 11:05 pm | 評論 (0)
jdbc基礎(二)
因為是基礎篇,所以還是對每一步驟簡單說明一下吧:
前面說是,注冊驅動程序有多方法,class.forname();是一種顯式地加載.當一個驅
動程序類被classloader裝載后,在溶解的過程中,drivermanager會注冊這個驅動類的實例.
這個調用是自動發生的,也就是說drivermanager.registerdriver()方法被自動調用了,當然
我們也可以直接調用drivermanager.registerdriver()來注冊驅動程序,但是,以我的經驗.
ms的瀏覽中applet在調用這個方法時不能成功,也就是說ms在瀏覽器中內置的jvm對該方法的
實現是無效的.
另外我們還可以利用系統屬性jdbc.drivers來加載多個驅動程序:
system.setproperty(/"jdbc.drivers/",/"driver1:driver2:.....:drivern/");多個驅動程序之
間用/":/"隔開,這樣在連結時jdbc會按順序搜索,直到找到第一個能成功連結指定的url的驅動
程序.
在基礎篇里我們先不介紹datasource這些高級特性.
在成功注冊驅動程序后,我們就可以用drivermanager的靜態方法getconnection來得
到和數據庫連結的引用:
connection conn = drivermanager.getconnection(url);
如果連結是成功的,則返回connection對象conn,如果為null或拋出異常,則說明沒有
和數據庫建立連結.
對于getconnection()方法有三個重載的方法,一種是最簡單的只給出數據源即:
getconnection(url),另一種是同時給出一些數據源信息即getconnection(url,properties),
另外一種就是給出數據源,用戶名和密碼:getconnection(url,user,passwod),對于數據源信息.
如果我們想在連結時給出更多的信息可以把這些信息壓入到一個properties,當然可以直接壓
入用戶名密碼,別外還可以壓入指定字符集,編碼方式或默認操作等一些其它信息.
在得到一個連結后,也就是有了和數據庫找交道的通道.我們就可以做我們想要的操
作了.
還是先來介紹一些一般性的操作:
如果我們要對數據庫中的表進行操作,要先緣故綁定一個語句:
statement stmt = conn.createstatement();
然后利用這個語句來執行操作.根本操作目的,可以有兩種結果返回,如果執行的查詢
操作,返回為結果集resultset,如果執行更新操作,則返回操作的記錄數int.
注意,sql操作嚴格區分只有兩個,一種就是讀操作(查詢操作),另一種就是寫操作(更
新操作),所以,create,insert,update,drop,delete等對數據有改寫行為的操作都是更新操作.
resultset rs = stmt.executequery(/"select * from table where xxxxx/");
int x = stmt.executeupdate(/"delete from table where ....../");
如果你硬要用executequery執行一個更新操作是可以的,但不要把它賦給一個句柄,
當然稍微有些經驗的程序員是不會這么做的.
至于對結果集的處理,我們放在下一節討論,因為它是可操作的可選項,只有查詢操作
才返回結果集,對于一次操作過程的完成,一個非常必要的步驟是關閉數據庫連結,在你沒有了
解更多的jdbc知識這前,你先把這一步驟作為jdbc操作中最最重要的一步,在以后的介紹中我會
不斷地提醒你去關閉數據庫連結!!!!!!!!!!!
按上面介紹的步驟,一個完成的例子是這樣的:(注意,為了按上面的步驟介紹,這個例
子不是最好的)
try{
class.forname(/"org.gjt.mm.mysql.driver/");
}catch(exception e){
system.out.println(/"沒有成功加載驅動程序:/"+e.tostring());
return;
}//對于象我這樣的經驗,可以直接從e.tostring()的簡單的幾個字判斷出異常原因,
//如果你是一個新手應該選捕獲它的子類,如何知道要捕獲哪幾個異常呢?一個簡單
//的方法就是先不加try{},直接class.forname(/"org.gjt.mm.mysql.driver/");,編
//譯器就會告訴你要你捕獲哪幾個異常了,當然這是偷機取巧的方法,最好還是自己
//去看jdk文檔,它會告訴你每個方法有哪些異常要你捕獲.
connection conn = null;
try{
conn = drivermanager.getconnection(
/"jdbc:mysql://host:3306/mysql/",
/"user/",
/"passwd/");
statement stmt = conn.createstatement();
resultset rs = stmt.executequery(/"select * from table/");
//rs 處理
[rs.close();]
[stmt.close();]
}
catch(exception e){
system.out.println(/"數據庫操作出現異常:/"+e.tostring());
}
finally{
try{conn.close();}catch(exception){}
}//不管你以前是學習到的關于數據庫流程是如何操作的,如果你相信我,從現在開始,
//請你一定要把數據庫關閉的代碼寫到finally塊中,切切!
發表于 @ 2006年03月17日 11:04 pm | 評論 (0)
jdbc基礎(一)
本來不想寫這部份入門級的內容,但既然欄目定為jdbc專欄,還是簡單寫一些吧.
jdbc基礎(一)
來,我們認識一下!
jdbc,java平臺的database的連通性.白話一句,什么意思啊?
就是java平臺上和數據庫進行連結的/"工具/".
還是先一起來回顧一下接口吧:從下向上,接口是對/"案例/"的抽象,由一個案例抽象出一些規則.
反過來,從上向下,被抽象出來的接口是對案例的一種承諾和約束.
也就是說,只要你實現我規定的接口,你的類就已經具有了接口對外承諾的方法,只要/"客戶/"會
操作接口,不需要重新學習就會操作實現了該接口的新類!
好了,用行話來說:
1.通過接口可以實現不相關的類的相同行為.
2.通過接口可以指明多個類需要實現的方法.
3.通過接口可以了解對象的交互方法而不需要了解對象所對應的類藍本.
這幾句話很明白吧?好象有一本什么模式的書把這段話用了30多頁寫出來,結果別人看了還不如
我這幾句話明白,不過我明白了為什么有些人要寫書了.
搞懂了以上這東西,jdbc就好明白了.
為了通用,java中要求有一種機制,在操作不同廠商數據庫時有相同的方法去操作,而不是每接
觸一種數據庫就要學習新的方法.完成這種機制的/"東西/"就叫/"jdbc/"了.
簡單地分,jdbc有兩部分組成,jdbc api和jdbc driver interface.
jdbc api就是提供給/"客戶/"(就是象你我這種菜鳥級程序員來用的,如果是高手都自己寫jdbc了,
哈哈)的一組獨立于數據庫的api,對任何數據庫的操作,都可以用這組api來進行.那么要把這些通用的api
翻譯成特定數據庫能懂的/"指令/",就要由jdbc driver interface來實現了,所以這部份是面向jdbc驅動程
序開發商的編程接口,它會把我們通過jdbc api發給數據庫的通用指令翻譯給他們自己的數據庫.
還是通過實際操作來看看jdbc如何工作的吧.
因為jdbc api是通用接口,那么程序是如何知道我要連結的是哪種數據庫呢?所以在和數據庫連
結時先要加載(或注冊可用的driver),其實就是jdbc簽名.加載驅動程序和好多方法,最常用的就是先把驅
動程序類溶解到內存中,作為/"當前/"驅動程序.注意/"當前/"是說內存中可以有多個驅動程序,但只有現在加
載的這個作為首選連結的驅動程序.
class.forname(/"org.gjt.mm.mysql.driver/");
class.forname方法是先在內存中溶解簽名為/"org.gjt.mm.mysql.driver/"的driver類,driver類
就會把相應的實現類對應到jdbc api的接口中.比如把org.gjt.mm.mysql.connection的實例對象賦給
java.sql.connection接口句柄,以便/"客戶/"能通過操作java.sql.connection句柄來調用實際的
org.gjt.mm.mysql.connection中的方法.之于它們是如果映射的,這是廠商編程的,/"客戶/"只要調用
class.forname(/"org.gjt.mm.mysql.driver/");方法就可以順利地操作jdbc api了.
一個普通數據庫的連結過程為:
1.加載驅動程序.
2.通過drivermanager到得一個與數據庫連結的句柄.
3.通過連結句柄綁定要執行的語句.
4.接收執行結果.
5.可選的對結果的處理.
6.必要的關閉和數據庫的連結.
發表于 @ 2006年03月17日 11:00 pm | 評論 (0)
如何使用javabean操作數據庫?高效bean封裝
你平時是如何使用jsp操作數據庫呢?對于jsp+javabean模式,想必大家都已經很熟悉了,我們可以將獲取數據庫連接,查詢,更新甚至將其它的功能都封裝進javabean----
好了--下面讓我們來好好弄清楚一個問題:**你如何在jsp頁中取得db中的數據?從javabean中返回resultset,然后在jsp中枚舉嗎?如果是這樣的話,那我強烈建議你把這篇文章讀完。*'*
用javabean封裝數據庫操作誰不會?--對啊,大家都會,但是--如果構建一個高擴展性的“結構”?這就要用到java的相關知識了。廢話少說,我們先在tomcat中創建一個datasource- jdbc/panabia,然后再創建一個java“基類”,這個類封裝了數據庫連接和連接的釋放:[程式中有相應的注解]
code:
--------------------------------------------------------------------------------
package panabia.db;
import javax.sql.datasource;
import javax.naming.*;
import java.sql.*;
public class sqlfactory
{
private static datasource ds=null;
private static object lock=new object();
//生成datasource**
public static datasource gaindatasource(){
try{
if(ds==null){
synchronized(lock){
if(ds==null){
context ctx=new initialcontext();
ds=(datasource)ctx.lookup(/"java:comp/env/jdbc/panabia/");
}
}
}
}
catch(namingexception e){e.printstacktrace();}
return ds;
}
//生成sql連接**
public static synchronized connection gainconnection(){
connection con=null;
try{
if(ds==null){
gaindatasource();
}
con=ds.getconnection();
}
catch(sqlexception e){e.printstacktrace();}
return con;
}
//釋放sql連接**
public static void releaseconnection(resultset rs,preparedstatement ps,statement sql,connection con){
try{
if(rs!=null)
rs.close();
}
catch(sqlexception e){e.printstacktrace();}
try{
if(ps!=null)
ps.close();
}
catch(sqlexception e){e.printstacktrace();}
try{
if(sql!=null)
sql.close();
}
catch(sqlexception e){e.printstacktrace();}
try{
if(con!=null&&!con.isclosed())
con.close();
}
catch(sqlexception e){e.printstacktrace();}
}
}
--------------------------------------------------------------------------------
大家都應該注意到了,這個類的所有的方法全部是static的,之所以這樣,主要是為了方便其它“擴展類”的調用,當然,還有其它好處--- :)
好了,這個類就封裝完畢了,現在我們就可以針對不同的應用要求單獨寫javabean了,比如一個簡單的:在jsp中列出verify表中的所有用戶名與密碼列表-
該怎么做?--使用sqlfactory生成connection,再生成statement,再生成resultset--然后枚舉嗎?好象不錯,哦,等等......這樣做你難道沒有一種“非常親切”的感覺嗎?---對了,asp,php中就是如此-faint~我們怎么又回到“原始社會”了....
有沒有更好的方式?答案是肯定的,java的能力是“通天”的強大,只要你能想得到,仔細看看它的api document,就不難找出解決辦法。
答案出來了:
我們在查詢類中返回iterator到jsp枚舉,而不是resultset。
好了,我們的userquery類就產生了:
code:
--------------------------------------------------------------------------------
package panabia.operate;
import panabia.db.sqlfactory;
import java.util.*;
import java.sql.*;
public class userquery{
private arraylist list=null;
private connection con=null;
private statement sql=null;
private resultset rs=null;
public iterator getresult(){
try{
con=sqlfactory.gainconnection();
sql=con.createstatement();
rs=sql.executequery(/"select * from verify/");
//verify表只有兩個字段:username,password;
list=new arraylist();
while(rs.next()){
list.add(rs.getstring(1));
list.add(rs.getstring(2));
}
}
catch(sqlexception e){e.printstacktrace();}
finally{sqlfactory.releaseconnection(rs,null,sql,con);}
return list.iterator();
}
}
--------------------------------------------------------------------------------
然后,就是在jsp頁中進行數據的枚舉:因為發現cnjbb不支持html標簽的顯示,所以,只貼出了jsp中的全部java代碼片--
........
iterator it=userquery.getresult();
while(it.hasnext()){
out.print((string)it.next());
}
..........
就是這么簡單,一個循環就搞定了。
我承認,就這樣把數據“裸列”出來實是“不雅”,想美化?---如果你會,那就開始做;如果覺得自己水平不行,你就需要找一個網頁美工來幫你了;和網頁美工配合并不是難事兒,只要將html標簽簡單的插入java scriptlet中就ok了--很明顯,這個比在jsp中枚舉resultset的情況好多了,不會“牽一發而動全身”。
尚不完善的地方:雖然情況好了一些,但當使用arraylist取出體積很大的數據時,會相當耗費系統資源[系統會在內存單獨開一塊空間存放結果]---相關的優化方法是有的,我在這里就不作陳述了,大家可以參考一下其它的相關資料。
新聞熱點
疑難解答