問題提出
現在幾乎所有的應用系統都無法避免使用數據庫系統。在JAVA世界里訪問數據庫是一件非常輕松的事情,JDBC為JAVA應用程序訪問數據庫提供了一個統一的接口,通過使用JDBC接口開發者無需關心系統最終采用哪種數據庫,因為JDBC僅僅是定義了訪問幾個JAVA的接口類,具體的實現是由數據庫廠商提供的,這種做法其實與其他數據庫連接方式例如ODBC是類似的。但是在實際的應用過程中,開發者發現離JDBC設計的初衷還是有一定距離,就比如說在存儲字符串時的編碼問題,我想很多開發者都會遇見這個問題,倒不是因為說解決它有什么技術方面的難度,而是它的的確確非常繁瑣。我們必須在每次寫入或者讀出字符串的時候進行編碼和反編碼處理;或者說我們可以寫一個方法可以進行編碼處理的,但又必須在每次數據庫操作的時候調用,雖然調用很簡單,可是我非得這樣嗎?要是忘了編碼那又要DEBUG了。當然你可能覺得這并沒有什么,或者你可能很勤快,喜歡寫大量重復的代碼,可是你難道沒有覺得這種繁瑣的工作正在浪費你過于寶貴的青春嗎?停止你的鍵盤輸入,讓我們來解決這個問題吧!
解決思路
在傳統的應用程序中數據庫操作部分我們可以想象成兩層,如圖所示:一個是數據庫的"連接池",另外一個業務數據操作層。在這里數據庫的連接池是廣義的,你可以把JDBC中的DriverManager也當成是連接池,具體的意思就是我們可以通過這層來獲取到指定數據庫的連接而不去關心它是怎么獲取的。如果這個時候數據庫系統(有如Informix,SQL Server)要求對字符串進行轉碼才能存儲(例如最常見的GBK->ISO8859_1轉碼),那我們就必須在業務數據操作層來進行,這樣有多少業務數據操作我們就要做多少編碼轉碼的工作,太麻煩了,代碼中充斥中大量重復的內容。本文提出的解決方案就是利用對獲取到的數據庫連接實例進行二次封裝,也就是在數據庫連接池與業務數據操作層之間加入了連接封裝層,當然了,我們也完全可以直接將連接封裝集成到數據庫連接池內部。關于連接池的實現請參照我的另外一篇文章《使用JAVA動態代理實現數據庫連接池》
圖一
我們知道進行編碼和轉碼工作都是集中在JDBC的兩個接口PreparedStatement和ResultSet上進行的,主要涉及PreparedStatement的setString方法以及ResultSet的getString方法。前面我們講過需要加入一個連接封裝層來對數據庫連接實例進行二次封裝,但是怎么通過這個封裝來改變PreparedStatement和ResultSet這兩個接口的行為呢?這個問題其實也很簡單,因為PreparedStatement接口必須通過Connection接口來獲取實例,而ResultSet接口又必須從Statement或者PreparedStatement接口來獲取實例,有了這樣的級聯關系,問題也就迎刃而解了。還是利用我在文章《使用JAVA動態代理實現數據庫連接池》中使用的動態接口代理技術。首先我們設計Connection接口的代理類_Connection,這個代理類接管了Connection接口中所有可能獲取到Statement或者PreparedStatement接口實例的方法,例如:prepareStatement和createStatement。改變這兩個方法使之返回的是經過接管后的Statement或者PreparedStatement實例。通過對于Statement接口也有相應的代理類_Statement,這個代理類接管用于獲取ResultSet接口實例的所有方法,包括對setString方法的接管以決定是否對字符串進行編碼處理。對于接口ResultSet的接管類_ResultSet就相應的比較簡單,它只需要處理getString方法即可。
關鍵代碼
前面我們大概介紹了這個解決方案的思路,下面我們給出關鍵的實現代碼包括Connection的代理類,Statement的代理類,ResultSet的代理類。這些代碼是在原來關于數據庫連接池實現的基礎上進行擴充使之增加對自動編碼處理的功能。有需要源碼打包的可以通過電子郵件跟我聯系。
_Connection.java
/*
?* Created on 2003-10-23 by Liudong
?*/
package lius.pool;
import java.sql.*;
import java.lang.reflect.*;
/*
?*
?* 數據庫連接的代理類
?* @author Liudong
?*/
?class _Connection implements InvocationHandler{
?private Connection conn = null;
?private boolean coding = false;
?//指定是否進行字符串轉碼操作
?_Connection(Connection conn, boolean coding){
??this.conn = conn;
??this.coding = coding;
??initConnectionParam(this.conn);
?
?}
?
?/**?
? * Returns the conn.?
? * @return Connection?
? */
?
?public Connection getConnection() {
??Class[] interfaces = conn.getClass().getInterfaces();
??if(interfaces==null||interfaces.length==0){
???interfaces = new Class[1];
???interfaces[0] = Connection.class;
??
??}
?
??Connection conn2 = (Connection)Proxy.newProxyInstance(?conn.getClass().getClassLoader(), interfaces,this);
??return conn2;
?
?}
?
?/**?
? * @see java.lang.reflect.InvocationHandler#invoke?
? */
?public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {?
??String method = m.getName();
??//調用相應的操作
??Object obj = null;
??try{
???obj = m.invoke(conn, args);
???//接管用于獲取語句句柄實例的方法
???if((CS.equals(method)||PS.equals(method))&&coding)
????return new _Statement((Statement)obj,true).getStatement();
??
??} catch(InvocationTargetException e) {
???throw e.getTargetException();
??}
??return obj;
?}
?
?private final static String PS = "prepareStatement";
?private final static String CS = "createStatement";
}
_Statement.java
/*
?* Created on 2003-10-23 by Liudong
?*/
?
package lius.pool;
import java.sql.*;
import java.lang.reflect.*;
/**
?* 數據庫語句對象實例的代理類
?* @author Liudong
?*/
class _Statement implements InvocationHandler{?
?private Statement statement ; //保存所接管對象的實例?
?private boolean decode = false; //指定是否進行字符串轉碼?
?public _Statement(Statement stmt,boolean decode) {?
??this.statement = stmt;
??this.decode = decode;
?}
?
?/**?
? * 獲取一個接管后的對象實例?
? * @return?
? */
?public Statement getStatement() {
??Class[] interfaces = statement.getClass().getInterfaces();
??if(interfaces==null||interfaces.length==0){?
???interfaces = new Class[1];
???interfaces[0] = Statement.class;
??}?
??Statement stmt = (Statement)Proxy.newProxyInstance(???
???statement.getClass().getClassLoader(),?
???interfaces,this);
??return stmt;
?
?}
?
?/**?
? * 方法接管?
? */
?public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
??String method = m.getName(); //接管setString方法?
??if(decode && SETSTRING.equals(method)) {
???try{
????String param = (String)args[1];
????if(param!=null)
?????param = new String(param.getBytes(),"8859_1");
????return m.invoke(statement,new Object[]{args[0],param});
???} catch(InvocationTargetException e){
????throw e.getTargetException();
????????
???}??
??}
??
??//接管executeQuery方法
??
??if(decode && EXECUTEQUERY.equals(method)){
???try{
????ResultSet rs = (ResultSet)m.invoke(statement,args);
????return new _ResultSet(rs,decode).getResultSet();
???
???}catch(InvocationTargetException e){
????throw e.getTargetException();
????}??
??}
??
??try{
???return m.invoke(statement, args);
??} catch(InvocationTargetException e) {
???throw e.getTargetException();
???}?
?}
?//兩個要接管的方法名
?
?private final static String SETSTRING = "setString";
?private final static String EXECUTEQUERY = "executeQuery";
}
_ResultSet.java
/*
?* Created on 2003-10-23 by Liudong
?*/
?
package lius.pool;
import java.sql.ResultSet;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
?* 數據庫結果集的代理類
?* @author Liudong
?*/
?class _ResultSet implements InvocationHandler{?
?private ResultSet rs = null;
?private boolean decode = false;
?
?public _ResultSet(ResultSet rs,boolean decode) {
??this.rs = rs;
??this.decode = decode;
?}
?
?public ResultSet getResultSet(){?
??Class[] interfaces = rs.getClass().getInterfaces();
??if(interfaces==null||interfaces.length==0){
???interfaces = new Class[1];
???interfaces[0] = ResultSet.class;??
??}
?
??ResultSet rs2 = (ResultSet)Proxy.newProxyInstance(rs.getClass().getClassLoader(),interfaces,this);
??return rs2;
?
?}
?/**?
? * 結果getString方法?
? */
?public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {?
??String method = m.getName();
??if(decode && GETSTRING.equals(method)){
???try{
????String result = (String)m.invoke(rs,args);
????if(result!=null)?????
?????return new String(result.getBytes("8859_1"));
????return null;
???
???}catch(InvocationTargetException e){
????throw e.getTargetException();
????}
???
??}?
??
??try{
???return m.invoke(rs, args);
??}catch(InvocationTargetException e){
???throw e.getTargetException();
??}
?}
?
?private final static String GETSTRING = "getString";
}
現在我們已經把三個接口的代理類做好了,下一步就是怎么來使用這三個類。其實對于使用者來講并不需要關心三個類,只需要了解_Connection就可以了,因為另外兩個是_Connection直接調用的。為了使用_Connection我們必須傳入兩個參數,第一個是數據庫實際的數據庫連接實例,另外一個是布爾值代表是否進行轉碼處理。我們必須先通過實際的情況獲取到數據庫連接后再傳入_Connection的構造函數作為參數,下面例子告訴你如何來使用_Connection這個類:
Connection conn = getConnection(); //獲取數據庫連接
boolean coding = false; //從配置或者其他地方讀取是否進行轉碼的配置?
//接管數據庫連接實例?
_Connection _conn = new _Connection(conn,coding);
//獲得接管后的數據庫連接實例,以后直接使用conn2而不是conn?
Connection conn2 = _conn.getConnection();
因為對一個應用系統來講,數據庫連接的獲取必然有統一的方法,在這個方法中加入對連接的接管就可以一勞永逸的解決數據庫的編碼問題。
性能比較
功能沒有問題了,開發者接下來就會關心性能的問題,因為在進行一些對響應速度要求很高或者大數據量的處理情況下性能就成為一個非常突出的問題。由于JAVA中的動態接口代理采用的是反射(Reflection)機制,同時又加入我們自己的一些代碼例如方法名判斷,字符串轉碼等操作因此在性能上肯定比不上直接使用沒有經過接管的數據庫連接。但是這點性能上的差別是不是我們可以忍受的呢,為此我做了一個試驗對二者進行了比較:
測試環境簡單描述:
使用ACCESS數據庫,建兩張結構一樣的表,計算從獲取連接后到插入數據完畢后的時間差,兩個程序(直連數據庫和使用連接接管)都進行的字符串的轉碼操作。
測試結果:
插入記錄數 |
直連數據庫程序耗時 單位:ms |
使用連接接管程序耗時 |
性能比較 |
1000 |
2063 |
2250 |
9.0% |
5000 |
8594 |
8359 |
-2.7% |
10000 |
16750 |
17219 |
2.8% |
15000 |
22187 |
23000 |
3.6% |
20000 |
27031 |
27813 |
2.9% |
從上面這張測試結果表中來看,二者的性能的差別非常小,盡管在兩萬條數據的批量插入的時候時間差別也不會多于一秒鐘,這樣的結果應該說還是令人滿意的,畢竟為了程序良好的結構有時候犧牲一點點性能還是值得的。
本文算是我之前文章《使用JAVA動態代理實現數據庫連接池》中提出的數據庫連接池實現的進一步完善,同樣使用動態接口代理的技術來解決數據庫編碼的問題。JAVA的這個高級技術可以用來解決許多實際中非常棘手的問題,就像本文提到的編碼問題的處理以及數據庫連接池的實現,同時在WEB開發框架的實現上也有非常大的作為。歡迎對這方面感興趣的朋友來信共同來研究。
from: http://www.javafan.net/article/20041212111952983.html