(1)No possibility for resource leaks. Correct JDBC coding isn't difficult but it is time-consuming and tedious. This often leads to connection leaks that may be difficult to track down.
(2)Cleaner, clearer persistence code. The amount of code needed to persist data in a database is drastically reduced. The remaining code clearly expresses your intention without being cluttered with resource cleanup.
(3)Automatically populate JavaBean properties from ResultSets. You don't need to manually copy column values into bean instances by calling setter methods. Each row of the ResultSet can be represented by one fully populated bean instance.
(1)An Object/Relational bridge - there are plenty of good O/R tools already. DbUtils is for developers looking to use JDBC without all the mundane pieces.
(2)A Data Access Object (DAO) framework - DbUtils can be used to build a DAO framework though.
(3)An object oriented abstraction of general database objects like a Table, Column, or PrimaryKey.
(4)A heavyweight framework of any kind - the goal here is to be a straightforward and easy to use JDBC helper library.
DbUtils is intentionally a single jar distribution and relies only on a standard Java 1.6 or later JRE.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.
apache.
commons.
dbutils;
import
java.
beans.
IntrospectionException;
import
java.
beans.
Introspector;
import
java.
beans.
PropertyDescriptor;
import
java.
lang.
reflect.
InvocationTargetException;
import
java.
lang.
reflect.
Method;
import
java.
sql.
Connection;
import
java.
sql.
ParameterMetaData;
import
java.
sql.
PreparedStatement;
import
java.
sql.
ResultSet;
import
java.
sql.
SQLException;
import
java.
sql.
Statement;
import
java.
sql.
Types;
import
java.
util.
Arrays;
import
javax.
sql.
DataSource;
/**
* The base class for QueryRunner & AsyncQueryRunner. This class is thread safe.
* QueryRunner & AsyncQueryRunner的基礎類:準備PreparedStatement、Connection、
* 填充PreparedStatement中的「?」佔位符
* 關閉Connection、Statement、ResultSet
*
@since
1.4 (mostly extracted from QueryRunner)
*/
public
abstract
class
AbstractQueryRunner {
/**
* Is {
@link
ParameterMetaData#getParameterType(int)} broken (have we tried
* it yet)?
* getParameterType(int)不可用嗎?
*/
private
volatile
boolean
pmdKnownBroken
=
false;
/**
* The DataSource to retrieve connections from.
* 數據源,我們可以從此數據源中獲得數據庫連接。在使用dbutils時,要使用數據源。常用dbcp和c3p0
*
@deprecated
Access to this field should be through {
@link
#getDataSource()}.
* 這裏提示,要訪問數據源,建議調用getDataSource()方法
*/
@
Deprecated
protected
final
DataSource
ds;
/**
* Default constructor, sets pmdKnownBroken to false and ds to null.
* 默認構造函數中,設置數據源爲null
*/
public
AbstractQueryRunner() {
ds
=
null;
}
/**
* Constructor to control the use of <code>ParameterMetaData</code>.
* 此構造器中控制了ParameterMetaData的使用,在使用中,個人比較少使用這種方式
*
@param
pmdKnownBroken
* Some drivers don't support
* {
@link
ParameterMetaData#getParameterType(int) }; if
* <code>pmdKnownBroken</code> is set to true, we won't even try
* it; if false, we'll try it, and if it breaks, we'll remember
* not to use it again.
* 有一些驅動不支持getParameterType(int)。如果pmdKnownBroken設置爲true,我們不會去嘗試
* ,如果爲false,我們會嘗試調用。如果不行,我們會記住不要再去調用它。
*/
public
AbstractQueryRunner(
boolean
pmdKnownBroken) {
this.
pmdKnownBroken
=
pmdKnownBroken;
//這裏也設置了數據源爲null
ds
=
null;
}
/**
* Constructor to provide a <code>DataSource</code>. Methods that do not
* take a <code>Connection</code> parameter will retrieve connections from
* this <code>DataSource</code>.
* 使用此構造器,可以爲數據源賦值。沒有Connection參數的方法將從此數據源中獲得數據庫連接。
* 在使用dbutils時,經常使用這個構造器
*
*
@param
ds
* The <code>DataSource</code> to retrieve connections from.
*/
public
AbstractQueryRunner(
DataSource
ds) {
this.
ds
=
ds;
}
/**
* Constructor to provide a <code>DataSource</code> and control the use of
* <code>ParameterMetaData</code>. Methods that do not take a
* <code>Connection</code> parameter will retrieve connections from this
* <code>DataSource</code>.
* 這個就是上述兩個的綜合了,不多講了
*
@param
ds
* The <code>DataSource</code> to retrieve connections from.
*
@param
pmdKnownBroken
* Some drivers don't support
* {
@link
ParameterMetaData#getParameterType(int) }; if
* <code>pmdKnownBroken</code> is set to true, we won't even try
* it; if false, we'll try it, and if it breaks, we'll remember
* not to use it again.
*/
public
AbstractQueryRunner(
DataSource
ds,
boolean
pmdKnownBroken) {
this.
pmdKnownBroken
=
pmdKnownBroken;
this.
ds
=
ds;
}
/**
* Returns the <code>DataSource</code> this runner is using.
* <code>QueryRunner</code> methods always call this method to get the
* <code>DataSource</code> so subclasses can provide specialized behavior.
* 這個是數據源屬性的getter方法,上面建議訪問數據源時,通過此方法進行訪問
*
@return
DataSource the runner is using
*/
public
DataSource
getDataSource() {
return
this.
ds;
}
/**
* Some drivers don't support
* {
@link
ParameterMetaData#getParameterType(int) }; if
* <code>pmdKnownBroken</code> is set to true, we won't even try it; if
* false, we'll try it, and if it breaks, we'll remember not to use it
* again.
* 這個是pmdKnownBroken屬性的getter方法,由於是boolean,可以使用isPmdKnownBroken
*
@return
the flag to skip (or not)
* {
@link
ParameterMetaData#getParameterType(int) }
*/
public
boolean
isPmdKnownBroken() {
return
pmdKnownBroken;
}
/**
* Factory method that creates and initializes a
* <code>PreparedStatement</code> object for the given SQL.
* <code>QueryRunner</code> methods always call this method to prepare
* statements for them. Subclasses can override this method to provide
* special PreparedStatement configuration if needed. This implementation
* simply calls <code>conn.prepareStatement(sql)</code>.
* 工廠方法,爲給定的SQL創建和實例化一個PreparedStatement對象。QueryRunner(AbstractQueryRunner的
* 子類)的方法總會調用此方法來準備statements。需要的話,子類可以覆蓋此方法來提供特殊的PreparedStatement配置。
* 這裏只是簡單地調用了conn.prepareStatement(sql)。這是常見的JDBC,很容易明白。
*
@param
conn
* The <code>Connection</code> used to create the
* <code>PreparedStatement</code>
*
@param
sql
* The SQL statement to prepare.
*
@return
An initialized <code>PreparedStatement</code>.
* if a database access error occurs
*/
protected
PreparedStatement
prepareStatement(
Connection
conn,
String
sql)
throws
SQLException {
return
conn.
prepareStatement(
sql);
}
/**
* Factory method that creates and initializes a <code>Connection</code>
* object. <code>QueryRunner</code> methods always call this method to
* retrieve connections from its DataSource. Subclasses can override this
* method to provide special <code>Connection</code> configuration if
* needed. This implementation simply calls <code>ds.getConnection()</code>.
*
* 工廠方法,創建和實例化一個Connection對象。QueryRunner中的方法總會調用此方法從數據源中獲得數據庫
* 連接。需要的話,子類可以覆寫此方法來提供特殊的連接配置。這裏只是簡單地調用了數據源的getConnection()
*
@return
An initialized <code>Connection</code>.
* if a database access error occurs
*/
protected
Connection
prepareConnection()
throws
SQLException {
if (
this.
getDataSource()
==
null) {
throw
new
SQLException(
"QueryRunner requires a DataSource to be "
+
"invoked in this way, or a Connection should be passed in");
}
return
this.
getDataSource().
getConnection();
}
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given objects.
* 使用給定的對象,填充PreparedStatement中的參數
*
@param
stmt
* PreparedStatement to fill
*
@param
params
* Query replacement parameters; <code>null</code> is a valid
* value to pass in.
* if a database access error occurs
*/
public
void
fillStatement(
PreparedStatement
stmt,
Object...
params)
throws
SQLException {
// check the parameter count, if we can
ParameterMetaData
pmd
=
null;
if (
!
pmdKnownBroken) {
//pmdKnownBroken初始化值是false
pmd
=
stmt.
getParameterMetaData();
//獲得元數據
int
stmtCount
=
pmd.
getParameterCount();
//獲得佔位符的個數
int
paramsCount
=
params
==
null ?
0 :
params.
length;
//給定參數的個數
if (
stmtCount
!=
paramsCount) {
//如果佔位符和給定參數的個數不一致,報錯
throw
new
SQLException(
"Wrong number of parameters: expected "
+
stmtCount
+
", was given "
+
paramsCount);
}
}
// nothing to do here
if (
params
==
null) {
//給定參數爲null,什麼也不作
return;
}
// TODO else有些不明白
for (
int
i
=
0;
i
<
params.
length;
i
++) {
if (
params[
i]
!=
null) {
stmt.
setObject(
i
+
1,
params[
i]);
}
else {
// VARCHAR works with many drivers regardless
// of the actual column type. Oddly, NULL and
// OTHER don't work with Oracle's drivers.
int
sqlType
=
Types.
VARCHAR;
if (
!
pmdKnownBroken) {
try {
/*
* It's not possible for pmdKnownBroken to change from
* true to false, (once true, always true) so pmd cannot
* be null here.
*/
sqlType
=
pmd.
getParameterType(
i
+
1);
}
catch (
SQLException
e) {
pmdKnownBroken
=
true;
}
}
stmt.
setNull(
i
+
1,
sqlType);
}
}
}
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given object's bean property values.
*
*
@param
stmt
* PreparedStatement to fill
*
@param
bean
* a JavaBean object
*
@param
properties
* an ordered array of properties; this gives the order to insert
* values in the statement
* if a database access error occurs
*/
public
void
fillStatementWithBean(
PreparedStatement
stmt,
Object
bean,
PropertyDescriptor[]
properties)
throws
SQLException {
//定義一個Object屬性,存放代替SQL中佔位符的參數
Object[]
params
=
new
Object[
properties.
length];
//循環屬性描述符數組,屬性描述符要與SQL中佔位符一一對應
for (
int
i
=
0;
i
<
properties.
length;
i
++) {
PropertyDescriptor
property
=
properties[
i];
//獲得第i個屬性
Object
value
=
null;
//獲得第i個屬性的getter方法
Method
method
=
property.
getReadMethod();
//如果不存在getter方法,就報錯
if (
method
==
null) {
throw
new
RuntimeException(
"No read method for bean property "
+
bean.
getClass()
+
" "
+
property.
getName());
}
try {
//調用getter方法,獲得此屬性的值,因爲是get方法,不用參數
value
=
method.
invoke(
bean,
new
Object[
0]);
}
catch (
InvocationTargetException
e) {
throw
new
RuntimeException(
"Couldn't invoke method: "
+
method,
e);
}
catch (
IllegalArgumentException
e) {
throw
new
RuntimeException(
"Couldn't invoke method with 0 arguments: "
+
method,
e);
}
catch (
IllegalAccessException
e) {
throw
new
RuntimeException(
"Couldn't invoke method: "
+
method,
e);
}
//獲得了屬性值後就存放到Object數組中
params[
i]
=
value;
}
//最後,調用fillStatement來填充SQL中的佔位符
fillStatement(
stmt,
params);
}
/**
* Fill the <code>PreparedStatement</code> replacement parameters with the
* given object's bean property values.
* 此方法跟fillStatementWithBean(PreparedStatement stmt, Object bean,
* PropertyDescriptor[] properties)類似,一個是用了屬性描述符,這裏是直接
* 使用了屬性名。
* 在方法內部將屬性變成屬性描述符,然後即可。
*
*
@param
stmt
* PreparedStatement to fill
*
@param
bean
* A JavaBean object
*
@param
propertyNames
* An ordered array of property names (these should match the
* getters/setters); this gives the order to insert values in the
* statement
* If a database access error occurs
*/
public
void
fillStatementWithBean(
PreparedStatement
stmt,
Object
bean,
String...
propertyNames)
throws
SQLException {
PropertyDescriptor[]
descriptors;
try {
descriptors
=
Introspector.
getBeanInfo(
bean.
getClass())
.
getPropertyDescriptors();
}
catch (
IntrospectionException
e) {
throw
new
RuntimeException(
"Couldn't introspect bean "
+
bean.
getClass().
toString(),
e);
}
PropertyDescriptor[]
sorted
=
new
PropertyDescriptor[
propertyNames.
length];
for (
int
i
=
0;
i
<
propertyNames.
length;
i
++) {
String
propertyName
=
propertyNames[
i];
if (
propertyName
==
null) {
throw
new
NullPointerException(
"propertyName can't be null: "
+
i);
}
boolean
found
=
false;
for (
int
j
=
0;
j
<
descriptors.
length;
j
++) {
PropertyDescriptor
descriptor
=
descriptors[
j];
if (
propertyName.
equals(
descriptor.
getName())) {
sorted[
i]
=
descriptor;
found
=
true;
break;
}
}
if (
!
found) {
throw
new
RuntimeException(
"Couldn't find bean property: "
+
bean.
getClass()
+
" "
+
propertyName);
}
}
fillStatementWithBean(
stmt,
bean,
sorted);
}
/**
* Throws a new exception with a more informative error message.
*
*
@param
cause
* The original exception that will be chained to the new
* exception when it's rethrown.
*
*
@param
sql
* The query that was executing when the exception happened.
*
*
@param
params
* The query replacement parameters; <code>null</code> is a valid
* value to pass in.
*
* if a database access error occurs
*/
protected
void
rethrow(
SQLException
cause,
String
sql,
Object...
params)
throws
SQLException {
String
causeMessage
=
cause.
getMessage();
if (
causeMessage
==
null) {
causeMessage
=
"";
}
StringBuffer
msg
=
new
StringBuffer(
causeMessage);
msg.
append(
" Query: ");
msg.
append(
sql);
msg.
append(
" Parameters: ");
if (
params
==
null) {
msg.
append(
"[]");
}
else {
msg.
append(
Arrays.
deepToString(
params));
}
SQLException
e
=
new
SQLException(
msg.
toString(),
cause.
getSQLState(),
cause.
getErrorCode());
e.
setNextException(
cause);
throw
e;
}
/**
* Wrap the <code>ResultSet</code> in a decorator before processing it. This
* implementation returns the <code>ResultSet</code> it is given without any
* decoration.
*
* <p>
* Often, the implementation of this method can be done in an anonymous
* inner class like this:
* </p>
*
* <pre>
* QueryRunner run = new QueryRunner() {
* protected ResultSet wrap(ResultSet rs) {
* return StringTrimmedResultSet.wrap(rs);
* }
* };
* </pre>
*
*
@param
rs
* The <code>ResultSet</code> to decorate; never
* <code>null</code>.
*
@return
The <code>ResultSet</code> wrapped in some decorator.
*/
protected
ResultSet
wrap(
ResultSet
rs) {
//包裝結果集,這裏沒有進行任何的包裝
return
rs;
}
/**
* Close a <code>Connection</code>. This implementation avoids closing if
* null and does <strong>not</strong> suppress any exceptions. Subclasses
* can override to provide special handling like logging.
* 關閉Connection
*
@param
conn
* Connection to close
* if a database access error occurs
*/
protected
void
close(
Connection
conn)
throws
SQLException {
DbUtils.
close(
conn);
}
/**
* Close a <code>Statement</code>. This implementation avoids closing if
* null and does <strong>not</strong> suppress any exceptions. Subclasses
* can override to provide special handling like logging.
* 關閉Statement
*
@param
stmt
* Statement to close
* if a database access error occurs
*/
protected
void
close(
Statement
stmt)
throws
SQLException {
DbUtils.
close(
stmt);
}
/**
* Close a <code>ResultSet</code>. This implementation avoids closing if
* null and does <strong>not</strong> suppress any exceptions. Subclasses
* can override to provide special handling like logging.
* 關閉ResultSet
*
@param
rs
* ResultSet to close
* if a database access error occurs
*/
protected
void
close(
ResultSet
rs)
throws
SQLException {
DbUtils.
close(
rs);
}
}