Connection的含義
    Connection表示了一個(gè)和數(shù)據(jù)庫(kù)的鏈接,底層需要有操作系統(tǒng)的Socket支持,所以Connection是一種資源,既然是一種資源,就需要按照建立,打開,使用,關(guān)閉的順序合理的使用。
    Connection是Java數(shù)據(jù)庫(kù)操作的基礎(chǔ),是進(jìn)行一系列操作的基礎(chǔ),所有的派生的操作,例如Statement,PreparedStatement,ResultSet等都由Connection直接或者間接的衍生。

    如何獲得Connection呢?
    方法一,使用DriverManager類來(lái)獲取,前提條件是數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序需要在classpath下(即使用數(shù)據(jù)庫(kù)鏈接的程序按照J(rèn)ava的方式可以訪問(wèn)到)。
       Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@192.168.0.1:1521:ORCL", user, pwd );
    方法二,使用數(shù)據(jù)庫(kù)連接池來(lái)獲取
       什么是數(shù)據(jù)庫(kù)連接池呢,數(shù)據(jù)庫(kù)連接池是標(biāo)準(zhǔn)JavaEE容器的一種服務(wù),例如Webspher,Weblogic,Tomcat等,容器預(yù)先建立一些數(shù)據(jù) 庫(kù)鏈接,以便應(yīng)用程序使用的時(shí)候從中借取,注意有借有還,當(dāng)應(yīng)用程序使用完了之后會(huì)將數(shù)據(jù)庫(kù)鏈接還回連接池。(數(shù)據(jù)源配置請(qǐng)參考其他文檔)
       使用連接池的好處是,可以預(yù)先建立鏈接,減小在數(shù)據(jù)庫(kù)獲取上的相對(duì)時(shí)間。
       使用連接池獲取數(shù)據(jù)庫(kù)鏈接的方式為:
           InitialContext ctx = new InitialContext();
           DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
           Connection conn = ds.getConnection();
       由于在配置數(shù)據(jù)庫(kù)連接池的時(shí)候已經(jīng)定義了URL,用戶名,密碼等信息,所以在程序中使用的時(shí)候不需要傳入這些信息。

ConnectionManager定義
    Connection用來(lái)專門管理數(shù)據(jù)庫(kù)鏈接,通常情況下ConnectionManager只有一個(gè)方法,調(diào)用這個(gè)方法將返回一個(gè)Connection 的實(shí)例。通過(guò)ConnectionManager可以封裝Connection的獲取方式(例如開發(fā)的時(shí)候使用DriverManager,運(yùn)用的時(shí)候使 用DataSource的方式,但是不需要修改ConnectionManager之外的其他代碼)和追加Connection獲取之前之后的操作(例如 針對(duì)Connection的屬性的設(shè)置)。
    下面的代碼是一個(gè)ConnectionManager的代碼示例:

package com.jpleasure.jdbc.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {
   
    public static Connection getConnection() throws DaoException {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("", "", "");
        } catch (SQLException e) {
            throw new DaoException("can not get database connection", e);
        }
        return conn;
    }
}

    如果需要從開發(fā)模式變?yōu)檫\(yùn)用模式,只需要將上述代碼修改為:
package com.jpleasure.jdbc.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {
   
    public static Connection getConnection() throws DaoException {
        Connection conn = null;
        try {
             Context ctx = new InitialContext();
             DataSource ds = (DataSource)ctx.lookup("jdbc/dsname");
             conn = ds.getConnection();
          } catch(NamingException e) {
             throw new DaoException("can not find datasource", e);
          }catch (SQLException e) {
            throw new DaoException("can not get database connection", e);
        }
        return conn;
    }
}

如果需要預(yù)先設(shè)定Connection的一些屬性,也可以在上述代碼中設(shè)定,例如:
package com.jpleasure.jdbc.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {
   
    public static Connection getConnection() throws DaoException {
        Connection conn = null;
        try {
             Context ctx = new InitialContext();
             DataSource ds = (DataSource)ctx.lookup("jdbc/dsname");
             conn = ds.getConnection();
                conn.setAutoCommit(false);
          } catch(NamingException e) {
             throw new DaoException("can not find datasource", e);
          }catch (SQLException e) {
            throw new DaoException("can not get database connection", e);
        }
        return conn;
    }
}

CommonDao定義
    屬性和構(gòu)造方法
       通常情況下,CommonDao要有一個(gè)Connection的引用。所有一個(gè)CommonDao的實(shí)例的所有方法的調(diào)用都需要依賴于這個(gè) Connection。需要一個(gè)Connection的另外一個(gè)原因是如果各個(gè)方法需要保證在一個(gè)事務(wù)環(huán)境中(上下文中),必須保證所有的操作都在一個(gè) Connection上。
       構(gòu)造方法通常需要將類型為Connection的屬性實(shí)例化,例如:

package com.jpleasure.jdbc.dao;

import java.sql.Connection;

public class CommonDao {
   
    private Connection conn;
   
    public CommonDao() throws DaoException {
        this.conn = ConnectionManager.getConnection();
    }   
}
   
    事務(wù)方法
        begin()
          開始一個(gè)事務(wù),調(diào)用CommonDao的begin方法之后,所以的后續(xù)操作將會(huì)在一個(gè)事務(wù)環(huán)境內(nèi),要么全部提交,要么全部回滾。

        commit()
          提交一個(gè)事務(wù),必須在begin調(diào)用之后調(diào)用。且和rollback方法互斥。

        rollback()  
          回滾一個(gè)事務(wù),必須在begin方法調(diào)用之后調(diào)用。且和commit方法互斥。
   
       事務(wù)的實(shí)現(xiàn)有兩種方法,一種是使用基于單一Connection的事務(wù),另外一種方法是使用容器的JTA(Java Transaction API)。需要注意的是第一種方法可以在任何環(huán)境下使用,但是只能是針對(duì)單一的數(shù)據(jù)庫(kù)鏈接。第二種方法智能在支持JTA的Java EE容器中使用(例如Websphere,Weblogic等,Tomcat默認(rèn)不支持),但是支持多個(gè)Connection實(shí)例。
    第一種方法代碼為:
package com.jpleasure.jdbc.dao;

import java.sql.Connection;
import java.sql.SQLException;

public class CommonDao {
   
    private Connection conn;
   
    public CommonDao() throws DaoException {
        this.conn = ConnectionManager.getConnection();
    }   

    public void begin() throws DaoException{
        if(conn != null) {
            try {
                conn.setAutoCommit(false);
            } catch (SQLException e) {
                throw new DaoException("can not begin transaction", e);
            }
        } else {
            throw new DaoException("connection not opened!");
        }
    }
   
    public void commit() throws DaoException {
        try {
            if (conn != null && !conn.getAutoCommit()) {
                conn.commit();
                conn.setAutoCommit(true);
            } else {
                if (conn == null) {
                    throw new DaoException("connection not opened!");
                } else {
                    throw new DaoException("first begin then commit please!");
                }
            }
        } catch (SQLException e) {
            throw new DaoException("can not commit transaction!", e);
        }
    }
   
    public void rollback() throws DaoException {
        try {
            if (conn != null && !conn.getAutoCommit()) {
                conn.rollback();
                conn.setAutoCommit(true);
            } else {
                if (conn == null) {
                    throw new DaoException("connection not opened!");
                } else {
                    throw new DaoException("first begin then rollback please!");
                }
            }
        } catch (SQLException e) {
            throw new DaoException("can not rollback transaction!", e);
        }
    }
   
   
}

    第二種我們?cè)谑褂肈AO的實(shí)例中介紹如何使用(@TODO)
       新建兩個(gè)DAO,做不同的操作,使用JTA保證事務(wù)完整。

    查詢方法
       查詢方法也許是CommonDao最常用的方法,查詢方法需要將數(shù)據(jù)庫(kù)的結(jié)果返回給畫面。返回值我們一般不使用ResultSet,因?yàn)? ResultSet依賴于Connection,如果Connection關(guān)閉,ResultSet將不再有效,所以我們通常將ResultSet轉(zhuǎn)變?yōu)? 一個(gè)List之后返回。
       在說(shuō)明查詢方法之前,我們先說(shuō)說(shuō)如何將數(shù)據(jù)庫(kù)中的內(nèi)容放在List中,我們使用一個(gè)List表示一個(gè)查詢結(jié)果集合,使用一個(gè)Map表示集合中的一行,Map的key表示數(shù)據(jù)庫(kù)表的字段名字,Value表示數(shù)據(jù)庫(kù)字段的內(nèi)容。代碼為:
        private List convert(ResultSet rs) throws DaoException {

        // record list
        List retList = new ArrayList();

        try {
            ResultSetMetaData meta = rs.getMetaData();

            // column count
            int colCount = meta.getColumnCount();

            // each record
            while (rs.next()) {

                Map recordMap = new HashMap();

                // each column
                for (int i = 1; i <= colCount; i++) {
                    // column name
                    String name = meta.getColumnName(i);
                    // column value
                    Object value = rs.getObject(i);
                    // add column to record
                    recordMap.put(name, value);
                }
                // ad record to list
                retList.add(recordMap);
            }
        } catch (SQLException ex) {
            throw new DaoException("can not convert result set to list of map", ex);
        }
        return retList;
    }

    為了避免Sql注入的安全問(wèn)題,我們通常使用PreparedStatement,在使用PreparedStatement的時(shí)候涉及到如何將傳入?yún)?shù)設(shè)置到PreparedStatement上面,參看以下的共通方法:
    private void apply(PreparedStatement pstmt, List params) throws DaoException {
        try {
            // if params exist
            if (params != null && params.size() > 0) {
                // parameters iterator
                Iterator it = params.iterator();
               
                // parameter index
                int index = 1;
                while(it.hasNext()) {
                   
                    Object obj = it.next();
                    // if null set ""
                    if (obj == null) {
                        pstmt.setObject(index, "");
                    } else {
                        // else set object
                        pstmt.setObject(index, obj);
                    }
                   
                    //next index
                    index++;
                }
            }
        } catch (SQLException ex) {
            throw new DaoException("can not apply parameter", ex);
        }
    }

    接著我們繼續(xù)說(shuō)我們的查詢方法,有了上述兩個(gè)方法,我們的查詢方法就非常簡(jiǎn)單了:
    public List query(String sql, List params) throws DaoException {
        List result = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = conn.prepareStatement(sql);
            this.apply(pstmt, params);
            rs = pstmt.executeQuery();
            result = this.convert(rs);
        } catch (SQLException ex) {
            throw new DaoException("can not execute query", ex);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    // nothing
                }
            }
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    // nothing
                }
            }
        }

        return result;
    }
   
    特殊的查詢方法(返回單值)
    有時(shí)候?yàn)榱朔奖闶褂茫覀冃枰祷貑沃档漠a(chǎn)尋方法,例如 select max(id) from table_a, select count(id) from table_b等。以下的代碼使用了上述通用的查詢方法,代碼為:
    public Object queryOne(String sql, List params) throws DaoException {
        List list = this.query(sql, params);
       
        if(list == null || list.size() == 0) {
            throw new DaoException("data not exist");
        } else {
            Map record = (Map)list.get(0);
            if(record == null || record.size() == 0 ) {
                throw new DaoException("data not exist");
            } else {
                return record.values().toArray()[0];
            }
        }
    }
   
    更新,刪除,插入方法
    由于在JDBC中這三個(gè)方法都是用了一個(gè)execute完成,所以這里我們也使用一個(gè)方法來(lái)完成這些功能。代碼為:
    public int execute(String sql, List params) throws DaoException {
        int ret = 0;
        PreparedStatement pstmt = null;
        try {
            pstmt = conn.prepareStatement(sql);
            this.apply(pstmt, params);
            ret = pstmt.executeUpdate();
        }catch(SQLException ex) {
            throw new DaoException("", ex);
        } finally {
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    // nothing.
                }
            }
        }
       
        return ret;
    }
   
    批處理方法(查詢)
    有些時(shí)候?yàn)榱吮阌诓僮鳎枰淮尾樵兌鄺lSQL語(yǔ)句,我們稱之為批處理,實(shí)現(xiàn)參看以下方法,其中為了和query方法做區(qū)分,將參數(shù)和返回值都改為了數(shù)組形式。

    public List[] queryBatch(String[] sqlArray, List[] paramArray) throws DaoException {
        List rets = new ArrayList();
        if(sqlArray.length != paramArray.length) {
            throw new DaoException("sql size not equal parameter size");
        } else {
            for(int i = 0; i < sqlArray.length; i++) {
                String sql = sqlArray[i];
                List param = paramArray[i];
                List ret = this.query(sql, param);
                rets.add(ret);
            }
            return (List[])rets.toArray();
        }
    }


    批處理方法(更新)
    有些時(shí)候需要一次更新多條Sql語(yǔ)句,為了便于操作,添加了批處理更新操作,參看以下代碼,為了和更新方法區(qū)分,將參數(shù)和返回值都改為了數(shù)組形式。
    public int[] executeBatch(String[] sqlArray, List[] paramArray) throws DaoException {
        List rets = new ArrayList();
        if(sqlArray.length != paramArray.length) {
            throw new DaoException("sql size not equal parameter size");
        } else {
            for(int i = 0; i < sqlArray.length; i++) {
                int ret = this.execute(sqlArray[i], paramArray[i]);
                rets.add(new Integer(ret));
            }
           
            int[] retArray = new int[rets.size()];
            for(int i = 0; i < retArray.length; i++) {
                retArray[i] = ((Integer)rets.get(i)).intValue();
            }
           
            return retArray;
        }
    }


    資源釋放
    由于CommonDao有一個(gè)Connection的屬性,且Connection屬于稀缺資源,所以在CommonDao不需要在使用的時(shí)候需要顯示的關(guān)閉Connection。代碼如下:
    public void close() throws DaoException{
        try {
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            } else {
                if(conn == null) {
                    throw new DaoException("can not close null connection, first new then close");
                } else {
                    throw new DaoException("transaction is running, rollbakc or commit befor close please.");
                }
            }
        } catch (SQLException ex) {
            throw new DaoException("Can not close common dao");
        }
    }
      
JDBC工具類(JDBCUtil Class)
    在上述的代碼中我們看到有很多的無(wú)用的處理,例如:
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    // nothing.
                }
            }
    為什么要有這些處理呢?說(shuō)先這些處理發(fā)生的位置都是在正常處理完成之后,這些處理(例如pstmt.close())即使失敗也沒(méi)有影響,這個(gè)時(shí)候我們需 要做上述的無(wú)用處理,這正是JDBC API的一個(gè)小小的瑕疵。我們通常使用一個(gè)特殊的靜態(tài)工具來(lái)來(lái)做補(bǔ)充,例如:

package com.jpleasure.jdbc.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBCUtil {
    public void safelyClose(Connection conn) {
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                //
            }
        }
    }
    public void safelyClose(PreparedStatement pstmt) {
        if(pstmt != null) {
            try {
                pstmt.close();
            } catch (SQLException e) {
                //
            }
        }
    }
    public void safelyClose(ResultSet rs) {
        if(rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                //
            }
        }
    }
}
 
異常處理
    也許細(xì)心的你已經(jīng)發(fā)現(xiàn)了一個(gè)問(wèn)題,為什么所有拋出異常的地方我們都是將SQLException包裝在了DaoException之內(nèi)拋出呢,為什么不直 接拋出SQLException呢?有兩個(gè)原因,第一,可以細(xì)化,分類Exception拋出合適的異常,添加合適的消息,第二,隔離和Dao和業(yè)務(wù)邏輯 的耦合,可以方便的修改Dao層而不會(huì)影響到業(yè)務(wù)邏輯層。另外需要注意,DaoExcetion中可以包含SQLException,這個(gè)時(shí)候可以為客戶 提供更詳細(xì)的錯(cuò)誤信息,例如ORA-12524等內(nèi)容,但是很少見(jiàn)到。

package com.jpleasure.jdbc.dao;

public class DaoException extends Exception {

    public DaoException() {
        super();
    }

    public DaoException(String message, Throwable cause) {
        super(message, cause);
    }

    public DaoException(String message) {
        super(message);
    }

    public DaoException(Throwable cause) {
        super(cause);
    }
   
}

ExtJS教程- Hibernate教程-Struts2 教程-Lucene教程