DbUtils入門#
Apache出品的極為輕量級的Jdbc訪問框架,核心類只有兩個:QueryRunner和ResultSetHandler。
各類ResultSetHandler:
ArrayHandler:把結果集中的第一行數據轉成對象數組。
ArrayListHandler:把結果集中的每一行數據都轉成一個數組,再存放到List中。
BeanListHandler:將結果集中的每一行數據都封裝到一個對應的JavaBean實例中,存放到List里。
請參考孤傲蒼狼的博客javaweb學習總結(四十一)——Apache的DBUtils框架學習,牆裂推薦。
Detail#
測試樣例代碼##
package org.wit.ff.jdbc;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.wit.ff.jdbc.access.IDataAccessor;
import org.wit.ff.jdbc.converter.ParamsConverter;
import org.wit.ff.jdbc.sql.db.MySQLBuilder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Created by F.Fang on 2015/3/31.
* Version :2015/3/31
*/
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class CRUDTest extends AbstractJUnit4SpringContextTests{
@Autowired
private IDataAccessor dataAccessor;
@Test
public void query(){
MySQLBuilder builder = new MySQLBuilder();
// sql: select * from Audience limit 2
builder.SELECT("*").FROM("Audience").PAGE(0, 2);
String sql = builder.toString();
final Audience audience = new Audience();
audience.setName("ff");
// 也可以直接填寫SQL.
List<Audience> list = dataAccessor.query(sql, null, Audience.class);
System.out.println(list);
}
@Test
public void insert(){
// 批量新增.
String sql = "insert into audience(id,name,pay) values(?,?,?)";
Object[][] params = new Object[1][];
params[0] = new Object[]{100,"ff",100.1};
dataAccessor.insert(sql, params);
}
@Test
public void insertDate(){
// 試試日期.
String sql = "insert into test_date(id,current) values(?,?)";
Object[][] params = new Object[1][];
params[0] = new Object[]{100, new Date()};
dataAccessor.insert(sql, params);
}
@Test
public void insertWithParamsConverter(){
// 試試PramsConverter好用不.
ParamsConverter<Audience> converter = new ParamsConverter<Audience>() {
@Override
public Object[] convert(Audience audience) {
return new Object[]{audience.getId(), audience.getName(), audience.getPay()};
}
};
String sql = "insert into audience(id,name,pay) values(?,?,?)";
// 構造數據.
List<Audience> list = new ArrayList<>();
Audience audience1 = new Audience();
audience1.setId(250);
audience1.setName("ff");
audience1.setPay(1000.00);
Audience audience2 = new Audience();
audience2.setId(251);
audience2.setName("ff1");
audience2.setPay(1000.00);
list.add(audience1);
list.add(audience2);
dataAccessor.insert(sql, list, Audience.class, converter);
}
}
Audience
package org.wit.ff.jdbc;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* Created by F.Fang on 2015/2/16.
* Version :2015/2/16
*/
public class Audience {
private Integer id;
private String name;
private Double pay;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPay() {
return pay;
}
public void setPay(Double pay) {
this.pay = pay;
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
}
}
配置
<!-- 數據庫連接池 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.driverClass}"/>
<property name="url" value="${db.jdbcUrl}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean id="dataAccessor" class="org.wit.ff.jdbc.access.dbutils.DefaultDataAccessor">
<property name="dataSource" ref="dataSource"/>
</bean>
備注:實際數據源配置還是需要優化的
看上去還湊合,調用比較簡單,那么接下來看看具體的實現吧!
AbstractDataAccessor##
- QueryRunner無參構造對象
- 抽象獲取連接和關閉連接的方法
package org.wit.ff.jdbc.access.dbutils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.*;
import org.apache.commons.lang3.StringUtils;
import org.wit.ff.jdbc.converter.ParamsConverter;
import org.wit.ff.jdbc.access.IDataAccessor;
import org.wit.ff.jdbc.exception.DbUtilsDataAccessException;
import org.wit.ff.jdbc.id.IdGenerator;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* Created by F.Fang on 2015/3/31.
* Version :2015/3/31
*/
public abstract class AbstractDataAccessor implements IDataAccessor {
/**
* 獲取連接的方法時抽象的,目的是為了和事務綁定,開放Connection來源.
* @return
* @throws SQLException
*/
protected abstract Connection getConnection() throws SQLException;
/**
* 獲取連接的方法時抽象的,目的是為了和事務綁定,因為事務執行前后包含的sql邏輯可能不只一條, 不可在一條sql執行邏輯完成后關閉.
* @param conn
*/
protected abstract void closeConn(Connection conn);
@Override
public <T> List<T> query(String sql, Class<T> resultType){
return query(sql, null, resultType);
}
/**
* 查詢時BeanListHandler將結果集轉換成對象列表.
* @param sql 查詢語句
* @param params 查詢參數
* @param resultType 返回類型
* @param <T>
* @return
*/
@Override
public <T> List<T> query(String sql, Object[] params, Class<T> resultType) {
if (resultType == null || StringUtils.isBlank(sql)) {
throw new DbUtilsDataAccessException("resultType can't be null and sql can't be blank!");
}
QueryRunner runner = new QueryRunner();
Connection conn = null;
List<T> result = null;
try {
conn = getConnection();
if (params != null) {
result = (List<T>) runner.query(conn, sql, new BeanListHandler(resultType), params);
} else {
result = (List<T>) runner.query(conn, sql, new BeanListHandler(resultType));
}
} catch (Exception e) {
throw new DbUtilsDataAccessException("query error, sql is:" + sql, e);
} finally {
closeConn(conn);
}
return result;
}
/**
*
* @param sql insert語句.
* @param params 對象參數列表.
* @param paramsType 參數類型.
* @param converter 參數轉換器.
* @param <T>
*/
@Override
public <T> void insert(String sql, List<T> params, Class<T> paramsType, ParamsConverter<T> converter) {
if (StringUtils.isBlank(sql)) {
throw new DbUtilsDataAccessException("sql can't be blank!");
}
if (params == null || params.isEmpty()) {
throw new DbUtilsDataAccessException("params can't be null or empty!");
}
if (paramsType == null || converter == null) {
throw new DbUtilsDataAccessException("paramsType or converter can't be null!");
}
QueryRunner runner = new QueryRunner();
Connection conn = null;
ArrayListHandler handler = new ArrayListHandler();
List<Object[]> keys = null;
try {
conn = getConnection();
int len = params.size();
Object[][] arr = new Object[len][];
for (int i = 0; i < len; ++i) {
// 將單個對象處理成單行記錄,用數組表示.
arr[i] = converter.convert(params.get(i));
}
// 獲取主鍵.
keys = (List<Object[]>) runner.insertBatch(conn, sql, handler, arr);
if (keys != null && keys.size() == len) {
// implements IdGenerator.
// 如果當前對象實現了IdGenerator接口, 則執行主鍵賦值的邏輯, 賦值邏輯由用戶自定義在具體的對象類型當中
if (params.get(0) instanceof IdGenerator) {
for (int i = 0; i < len; ++i) {
// 對每一個對象執行主鍵賦值(解析)
((IdGenerator) params.get(i)).parseGenKey(keys.get(i));
}
}
}
} catch (Exception e) {
throw new DbUtilsDataAccessException("batch insert error, sql is:" + sql, e);
}finally {
closeConn(conn);
}
}
/**
* 批量插入.
* @param sql
* @param params
*/
@Override
public void insert(String sql, Object[][] params) {
if(params==null || StringUtils.isBlank(sql)){
throw new DbUtilsDataAccessException("params can't be null and sql can't be empty!");
}
QueryRunner runner = new QueryRunner();
Connection conn = null;
ArrayListHandler handler = new ArrayListHandler();
try {
conn = getConnection();
runner.insertBatch(conn, sql, handler, params);
} catch (Exception e) {
throw new DbUtilsDataAccessException("batch insert error, sql is:" + sql, e);
}finally {
closeConn(conn);
}
}
/**
* 單條插入.
* @param sql
* @param params
*/
@Override
public void insert(String sql, Object[] params) {
if(params==null || StringUtils.isBlank(sql)){
throw new DbUtilsDataAccessException("params can't be null and sql can't be empty!");
}
QueryRunner runner = new QueryRunner();
Connection conn = null;
ArrayHandler handler = new ArrayHandler();
try {
conn = getConnection();
runner.insert(conn, sql, handler, params);
} catch (Exception e) {
throw new DbUtilsDataAccessException("insert error, sql is:" + sql, e);
}finally {
closeConn(conn);
}
}
/**
* 批量更新.
* @param sql
* @param params
* @return
*/
@Override
public int[] update(String sql, Object[][] params) {
if(params==null || StringUtils.isBlank(sql)){
throw new DbUtilsDataAccessException("params can't be null and sql can't be empty!");
}
QueryRunner runner = new QueryRunner();
Connection conn = null;
int[] result = null;
try {
conn = getConnection();
result = runner.batch(conn, sql, params);
} catch (Exception e) {
throw new DbUtilsDataAccessException("batch update error, sql is:" + sql, e);
}finally {
closeConn(conn);
}
return result;
}
/**
* 單條更新.
* @param sql
* @param params
* @return
*/
@Override
public int update(String sql, Object[] params) {
QueryRunner runner = new QueryRunner();
Connection conn = null;
int result = 0;
try {
conn = getConnection();
if (params != null) {
result = runner.update(conn, sql, params);
} else {
result = runner.update(conn, sql);
}
} catch (Exception e) {
throw new DbUtilsDataAccessException("update error, sql is:" + sql, e);
}finally {
closeConn(conn);
}
return result;
}
/**
* 刪除,沒有必要批量.
* 即使是批量,也可以調用批量更新的方法.
* 事實上此方法並無太大必要,只是為了避免歧義而已.
* @param sql
* @param params
* @return
*/
@Override
public int delete(String sql, Object[] params) {
return update(sql, params);
}
}
DefaultDataAccessor##
package org.wit.ff.jdbc.access.dbutils;
import org.apache.commons.dbutils.DbUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by F.Fang on 2015/3/31.
* 默認以配置數據源的方式獲取連接.
* Version :2015/3/31
*/
public class DefaultDataAccessor extends AbstractDataAccessor {
private DataSource dataSource;
protected Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
@Override
protected void closeConn(Connection conn) {
try {
DbUtils.close(conn);
} catch (SQLException e) {
// do nothing!
}
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
DefaultTransactionDataAccessor##
package org.wit.ff.jdbc.access.dbutils;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Created by F.Fang on 2015/4/23.
* Version :2015/4/23
*/
public class DefaultTransactionDataAccessor extends AbstractDataAccessor {
private DataSource dataSource;
protected Connection getConnection() throws SQLException {
return DataSourceUtils.getConnection(dataSource);
// 雖然可以將連接綁定到事務,但是當外部循dataAccessor的方法時,循環調用時會產生多個連接.
//return DataSourceUtils.getConnection(dataSource);
// 以下寫法無法解決事務問題,無法將連接綁定到Spring 事務.
// return dataSource.getConnection();
}
@Override
protected void closeConn(Connection conn) {
// do nothing!
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
采用Spring事務集成方案,將Connection交給Spring管理,實際上Spring將Connection綁定到當前執行線程當中(以ConnectionHolder包裝Connection,ThreadLocal包裝ConnectionHolder綁定線程)
詳情參考DataSourceTransactionMananger的源代碼,最好在doBeign和doCleanupAfterCompletion執行斷點調試
備注:AbstractDataAccessor開放getConnection()和closeConnection方法的原因也是調用過程Connection的開啟和關閉不能由自身維持,否則會導致事務處理時Spring持有的Connection和AbstractDataAccessor執行方法的Connection不一致或在執行時拿到了關閉了的Connection對象。