Connection的含義
Connection表示了一個和數據庫的鏈接,底層需要有操作系統的Socket支持,所以Connection是一種資源,既然是一種資源,就需要按照建立,打開,使用,關閉的順序合理的使用。
Connection是Java數據庫操作的基礎,是進行一系列操作的基礎,所有的派生的操作,例如Statement,PreparedStatement,ResultSet等都由Connection直接或者間接的衍生。
如何獲得Connection呢?
方法一,使用DriverManager類來獲取,前提條件是數據庫驅動程序需要在classpath下(即使用數據庫鏈接的程序按照Java的方式可以訪問到)。
Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@192.168.0.1:1521:ORCL", user, pwd );
方法二,使用數據庫連接池來獲取
什么是數據庫連接池呢,數據庫連接池是標準JavaEE容器的一種服務,例如Webspher,Weblogic,Tomcat等,容器預先建立一些數據
庫鏈接,以便應用程序使用的時候從中借取,注意有借有還,當應用程序使用完了之后會將數據庫鏈接還回連接池。(數據源配置請參考其他文檔)
使用連接池的好處是,可以預先建立鏈接,減小在數據庫獲取上的相對時間。
使用連接池獲取數據庫鏈接的方式為:
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/DataSource");
Connection conn = ds.getConnection();
由于在配置數據庫連接池的時候已經定義了URL,用戶名,密碼等信息,所以在程序中使用的時候不需要傳入這些信息。
ConnectionManager定義
Connection用來專門管理數據庫鏈接,通常情況下ConnectionManager只有一個方法,調用這個方法將返回一個Connection
的實例。通過ConnectionManager可以封裝Connection的獲取方式(例如開發的時候使用DriverManager,運用的時候使
用DataSource的方式,但是不需要修改ConnectionManager之外的其他代碼)和追加Connection獲取之前之后的操作(例如
針對Connection的屬性的設置)。
下面的代碼是一個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;
}
}
如果需要從開發模式變為運用模式,只需要將上述代碼修改為:
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;
}
}
如果需要預先設定Connection的一些屬性,也可以在上述代碼中設定,例如:
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定義
屬性和構造方法
通常情況下,CommonDao要有一個Connection的引用。所有一個CommonDao的實例的所有方法的調用都需要依賴于這個
Connection。需要一個Connection的另外一個原因是如果各個方法需要保證在一個事務環境中(上下文中),必須保證所有的操作都在一個
Connection上。
構造方法通常需要將類型為Connection的屬性實例化,例如:
package com.jpleasure.jdbc.dao;
import java.sql.Connection;
public class CommonDao {
private Connection conn;
public CommonDao() throws DaoException {
this.conn = ConnectionManager.getConnection();
}
}
事務方法
begin()
開始一個事務,調用CommonDao的begin方法之后,所以的后續操作將會在一個事務環境內,要么全部提交,要么全部回滾。
commit()
提交一個事務,必須在begin調用之后調用。且和rollback方法互斥。
rollback()
回滾一個事務,必須在begin方法調用之后調用。且和commit方法互斥。
事務的實現有兩種方法,一種是使用基于單一Connection的事務,另外一種方法是使用容器的JTA(Java
Transaction
API)。需要注意的是第一種方法可以在任何環境下使用,但是只能是針對單一的數據庫鏈接。第二種方法智能在支持JTA的Java
EE容器中使用(例如Websphere,Weblogic等,Tomcat默認不支持),但是支持多個Connection實例。
第一種方法代碼為:
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);
}
}
}
第二種我們在使用DAO的實例中介紹如何使用(@TODO)
新建兩個DAO,做不同的操作,使用JTA保證事務完整。
查詢方法
查詢方法也許是CommonDao最常用的方法,查詢方法需要將數據庫的結果返回給畫面。返回值我們一般不使用ResultSet,因為
ResultSet依賴于Connection,如果Connection關閉,ResultSet將不再有效,所以我們通常將ResultSet轉變為
一個List之后返回。
在說明查詢方法之前,我們先說說如何將數據庫中的內容放在List中,我們使用一個List表示一個查詢結果集合,使用一個Map表示集合中的一行,Map的key表示數據庫表的字段名字,Value表示數據庫字段的內容。代碼為:
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注入的安全問題,我們通常使用PreparedStatement,在使用PreparedStatement的時候涉及到如何將傳入參數設置到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);
}
}
接著我們繼續說我們的查詢方法,有了上述兩個方法,我們的查詢方法就非常簡單了:
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;
}
特殊的查詢方法(返回單值)
有時候為了方便使用,我們需要返回單值的產尋方法,例如 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中這三個方法都是用了一個execute完成,所以這里我們也使用一個方法來完成這些功能。代碼為:
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;
}
批處理方法(查詢)
有些時候為了便于操作,需要一次查詢多條SQL語句,我們稱之為批處理,實現參看以下方法,其中為了和query方法做區分,將參數和返回值都改為了數組形式。
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();
}
}
批處理方法(更新)
有些時候需要一次更新多條Sql語句,為了便于操作,添加了批處理更新操作,參看以下代碼,為了和更新方法區分,將參數和返回值都改為了數組形式。
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有一個Connection的屬性,且Connection屬于稀缺資源,所以在CommonDao不需要在使用的時候需要顯示的關閉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)
在上述的代碼中我們看到有很多的無用的處理,例如:
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
// nothing.
}
}
為什么要有這些處理呢?說先這些處理發生的位置都是在正常處理完成之后,這些處理(例如pstmt.close())即使失敗也沒有影響,這個時候我們需
要做上述的無用處理,這正是JDBC API的一個小小的瑕疵。我們通常使用一個特殊的靜態工具來來做補充,例如:
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) {
//
}
}
}
}
異常處理
也許細心的你已經發現了一個問題,為什么所有拋出異常的地方我們都是將SQLException包裝在了DaoException之內拋出呢,為什么不直
接拋出SQLException呢?有兩個原因,第一,可以細化,分類Exception拋出合適的異常,添加合適的消息,第二,隔離和Dao和業務邏輯
的耦合,可以方便的修改Dao層而不會影響到業務邏輯層。另外需要注意,DaoExcetion中可以包含SQLException,這個時候可以為客戶
提供更詳細的錯誤信息,例如ORA-12524等內容,但是很少見到。
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教程