引用:http://blog.csdn.net/itblog/article/details/981136
到目前為目,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是一個屬性檔案,它包含定義連線池的鍵-值對。可供定義的
公用屬性如下:
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()以獲得該管理器的引用,此呼叫將增加客戶程式計數。客戶程式在關閉時呼叫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/xxw@192.168.1.50: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資料庫互動中三個不變的部分隔離開來。現在讓我們討論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 call
Object[] results = handleQuery(sql, pStmntValues, processor, conn);
//Close the connection
closeConn(conn);
//And return its results
return results;
}
protected Object[] handleQuery(String sql, Object[] pStmntValues,
ResultProcessor processor, Connection conn) {
//Get a prepared statement to use
PreparedStatement stmnt = null;
try {
//Get an actual prepared statement
stmnt = 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 statement
ResultSet rs = stmnt.executeQuery();
//Get the results from this query
Object[] results = processor.process(rs);
//Close out the statement only. The connection will be closed by the
//caller.
closeStmnt(stmnt);
//Return the results
return 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 exception
closeConn(conn);
closeStmnt(stmnt);
//And rethrow as our runtime exception
throw 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 index
for(int i = 0; i < values.length; i++) {
//If the object is our representation of a null value, then handle it separately
if(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 table
Column 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 users
List users = new ArrayList();
User user = null;
//If there were results returned, then process them
while(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 query
SQLProcessor 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 object
return (User) users[0];
}
這就是全部。我們只需 要一個處理類和一個簡單的方法,我們就可以無需進行直接的連線維護、語句和異常處理。此外,如果我們擁有另外一個查詢由使用者表中得到一行,例如通過使用者名稱 或者密碼,我們可以重新使用UserResultProcessor。我們只需要插入一個不同的SQL語句,並且可以重新使用以前方法的使用者處理器。由於 返回行的後設資料並不依賴查詢,所以我們可以重新使用結果處理器。
更新的架構
那麼資料庫更新又如何呢?我 們可以用類似的方法處理,只需要進行一些修改就可以了。首先,我們必須增加兩個新的方法到SQLProcessor類。它們類似 executeQuery()和handleQuery()方法,除了你無需處理結果集,你只需要將更新的行數作為呼叫的結果:
CODE:
public void executeUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor) {
//Get a connection
Connection conn = ConnectionManager.getConnection();
//Send it off to be executed
handleUpdate(sql, pStmntValues, processor, conn);
//Close the connection
closeConn(conn);
}
protected void handleUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor, Connection conn) {
//Get a prepared statement to use
PreparedStatement stmnt = null;
try {
//Get an actual prepared statement
stmnt = 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 statement
int rows = stmnt.executeUpdate();
//Now hand off the number of rows updated to the processor
processor.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 exception
closeConn(conn);
closeStmnt(stmnt);
//And rethrow as our exception
throw 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 sqlProcessor = new SQLProcessor();
//Use our get user SQL statement
sqlProcessor.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 resources
try {
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 resources
try {
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 above
public static void updateUsers(User[] users) {
//Get our transaction
SQLTransaction trans = sqlProcessor.startTransaction();
//For each user, update it
User 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 transaction
trans.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)+/"//t/");
}
out.println();
while(rs.next()){
for(int i=1;i<=j;i++){
out.print(rs.getString(i)+/"//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());
}
..........
就是這麼簡單,一個迴圈就搞定了。