一、commons-dbutils简介
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。因此dbutils成为很多不喜欢hibernate的公司的首选。
commons-dbutilsAPI介绍:
- org.apache.commons.dbutils.QueryRunner
- org.apache.commons.dbutils.ResultSetHandler
工具类
- org.apache.commons.dbutils.DbUtils
二、QueryRunner类使用讲解
该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
QueryRunner类提供了两个构造方法:
- 默认的构造方法
- 需要一个 javax.sql.DataSource 来作参数的构造方法。
2.1、QueryRunner类的主要方法
public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException: 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 执行一个不需要置换参数的查询操作。
public int update(Connection conn, String sql, Object[] params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
public int update(Connection conn, String sql) throws SQLException:用来执行一个不需要置换参数的更新操作。
2.2、使用QueryRunner类实现CRUD
1.建立测试表:
create table users(
id int primary key auto_increment,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
);
2.建立JavaBean
package cn.zy.dbutils; import java.util.Date; public class User {
private int id;
private String name;
private String password;
private String email;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [birthday=" + birthday + ", email=" + email + ", id=" + id
+ ", name=" + name + ", password=" + password + "]";
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
3.建立测试类
package cn.zy.test; import java.sql.SQLException;
import java.util.Date;
import java.util.List; import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test; import cn.zy.dbutils.User;
import cn.zy.utils.JdbcUtils_C3P0; public class QueryRunnerCRUDTest {
@Test
public void add() throws SQLException{
//将数据源传递给QueryRunner,QueryRunner内部通过数据源获取数据库连接
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
Object param[] = {"张三","123","zhansan@qq.com",new Date(0)};
qr.update(sql, param);
} @Test
public void delete() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "delete from users where id=?";
qr.update(sql,1);
} @Test
public void update() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "update users set name=? where id=?";
Object param[] = {"王五",3};
qr.update(sql, param);
} @Test
public void find() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users where id=?";
Object params[] = {2};
User user = (User) qr.query(sql, params, new BeanHandler(User.class));
System.out.println(user);
} @Test
public void getAll() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
List list = (List) qr.query(sql, new BeanListHandler(User.class));
System.out.println(list.size());
} @Test
public void testBatch() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)";
Object params[][] = new Object[10][];
for (int i=0;i<10;i++){
params[i] = new Object[] { "aa" + i, "123", "aa@sina.com",
new Date() };
}
qr.batch(sql, params);
}
}
三、ResultSetHandler接口使用讲解
该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。
ResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)
3.1、ResultSetHandler接口的实现类
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
Scalar处理器
3.2、测试dbutils各种类型的处理器
package cn.zy.test; import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test; import cn.zy.utils.JdbcUtils_C3P0; public class ResultSetHandlerTest {
@Test
public void testArrayHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select *from users";
Object result[] = (Object[]) qr.query(sql, new ArrayHandler());
System.out.println(Arrays.asList(result));
} @Test
public void testArrayListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
List<Object[]> list = (List) qr.query(sql, new ArrayListHandler());
for(Object[] o : list){
System.out.println(Arrays.asList(o));
}
}
@Test
public void testColumnListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
List list = (List) qr.query(sql, new ColumnListHandler("id"));
System.out.println(list);
} @Test
public void testKeyedHandler() throws Exception{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
Map<Integer,Map> map = (Map) qr.query(sql, new KeyedHandler("id"));
for(Map.Entry<Integer, Map> me : map.entrySet()){
int id = me.getKey();
Map<String,Object> innermap = me.getValue();
for(Map.Entry<String, Object> innerme : innermap.entrySet()){
String columnName = innerme.getKey();
Object value = innerme.getValue();
System.out.println(columnName + "=" + value);
}
System.out.println("----------------");
}
} @Test
public void testMapHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
Map<String,Object> map = (Map) qr.query(sql, new MapHandler());
for(Map.Entry<String, Object> me : map.entrySet()){
System.out.println(me.getKey() + "=" + me.getValue());
}
} @Test
public void testMapListHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select * from users";
List<Map> list = (List) qr.query(sql, new MapListHandler());
for(Map<String,Object> map :list){
for(Map.Entry<String, Object> me : map.entrySet())
System.out.println(me.getKey() + "=" + me.getValue());
}
} @Test
public void testScalarHandler() throws SQLException{
QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource());
String sql = "select count(*) from users";
int count = ((Long)qr.query(sql, new ScalarHandler(1))).intValue();
System.out.println(count);
} }
依次返回:
====================================
====================================
====================================
+++++++++++++++++++++++++++++++++++
===================================
++++++++++++++++++++++++++++++++++
三、DbUtils类使用讲解
DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:
public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
四、JDBC开发中的事务处理
4.1、在业务层(BusinessService)处理事务
建立表:
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
); INSERT INTO account(NAME,money) VALUES('A',1000);
INSERT INTO account(NAME,money) VALUES('B',1000);
INSERT INTO account(NAME,money) VALUES('C',1000);
domain层
package cn.zy.domain; import java.util.Date; public class Account {
private int id;
private String name;
private Float money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Account [id=" + id + ", money=" + money + ", name=" + name
+ "]";
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
public Account(int id, String name, Float money) {
super();
this.id = id;
this.name = name;
this.money = money;
}
public Account() {
super();
} }
写一个Dao类进行进行CURD操作
package cn.zy.dao; import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import cn.zy.domain.Account; public class AccountDao { //接受service层传递过来的Connection对象
private Connection conn = null; public AccountDao(Connection conn){
this.conn = conn;
}
public AccountDao(){ } public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()}; //使用service层传递过来的Connection对象操作数据库
qr.update(conn, sql, params);
System.out.println("更新成功");
} public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//使用service层传递过来的Connection对象操作数据库
return (Account) qr.query(conn,sql, id, new BeanHandler(Account.class)); } }
在业务层实现转账
package cn.zy.service; import java.sql.Connection;
import java.sql.SQLException;
import cn.zy.dao.AccountDao;
import zn.zy.domain.Account;
import zn.zy.util.JdbcUtils; /**
* @ClassName: AccountService
* @Description: 业务逻辑处理层
*/
public class AccountService { /**
* @Method: transfer
* @Description:这个方法是用来处理两个用户之间的转账业务
* @param sourceid
* @param tartgetid
* @param money
* @throws SQLException
*/
public void transfer(int sourceid,int tartgetid,float money) throws SQLException{
Connection conn = null;
try{
//获取数据库连接
conn = JdbcUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//将获取到的Connection传递给AccountDao,保证dao层使用的是同一个Connection对象操作数据库
AccountDao dao = new AccountDao(conn);
Account source = dao.find(sourceid);
Account target = dao.find(tartgetid); source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money); dao.update(source);
//模拟程序出现异常让事务回滚
int x = 1/0;
dao.update(target);
//提交事务
conn.commit();
}catch (Exception e) {
e.printStackTrace();
//出现异常之后就回滚事务
conn.rollback();
}finally{
conn.close();
}
}
}
测试:
package cn.zy.test; import java.sql.SQLException;
import org.junit.Test;
import cn.zy.service.AccountService; public class AccountServiceTest {
@Test
public void fun() throws SQLException{
AccountService service = new AccountService();
service.transfer(1, 2, 100f);
}
}
4.2、使用ThreadLocal进行更加优雅的事务处理
上面的在businessService层这种处理事务的方式依然不够优雅,为了能够让事务处理更加优雅,我们使用ThreadLocal类进行改造,ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了
ThreadLocal类的使用范例如下:
package me.gacl.test; public class ThreadLocalTest { public static void main(String[] args) {
//得到程序运行时的当前线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread);
//ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来
ThreadLocal<String> t = new ThreadLocal<String>();
//把某个对象绑定到当前线程上 对象以键值对的形式存储到一个Map集合中,对象的的key是当前的线程,如: map(currentThread,"aaa")
t.set("aaa");
//获取绑定到当前线程中的对象
String value = t.get();
//输出value的值是aaa
System.out.println(value);
}
}
使用使用ThreadLocal类进行改造数据库连接工具类JdbcUtils,改造后的代码如下:
package cn.zy.utils; import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource; public class JdbcUtils_C3P0_v2 {
private static ComboPooledDataSource ds = null;
//使用ThreadLoacal存储当前线程中的Connection对象
private static ThreadLocal<Connection> threadLocal =new ThreadLocal<Connection>(); //在静态块中创建连接池
static{
try {
//使用C3P0的命名配置来创建数据源
ds = new ComboPooledDataSource("MySQL");
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
} /*
* 从数据源中获取数据库连接
*/
public static Connection getConnection() throws SQLException{
//从当前线程中获取数据库连接
Connection conn = threadLocal.get();
if(conn==null){
//从数据源中获取连接
conn = ds.getConnection();
//将conn绑定到当前线程
threadLocal.set(conn);
}
return conn;
} /*
* 开启事务
*/
public static void startTransaction(){
try {
Connection conn = threadLocal.get();
if(conn==null){
conn = getConnection();
//把conn绑定到当前线程上
threadLocal.set(conn);
}
//开启事务
conn.setAutoCommit(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
} /*
* 回滚事务
*/
public static void rollback(){
try {
Connection conn = threadLocal.get();
if(conn!=null){
//回滚事务
conn.rollback();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} /*
* 提交事务
*/
public static void commit(){
try {
Connection conn = threadLocal.get();
if(conn!=null){
//提交事务
conn.commit();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} /*
* 关闭数据库连接
*/
public static void close(){
try {
//从当前线程获取连接
Connection conn = threadLocal.get();
if(conn!=null){
conn.close();
//从当前线程接解除定
threadLocal.remove();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} /*
* 获取数据源
*/
public static DataSource getDataSource(){
return ds;
}
}
对AccountDao进行改造,数据库连接对象不再需要service层传递过来,而是直接从JdbcUtils2提供的getConnection方法去获取,改造后的AccountDao如下:
package cn.zy.dao; import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import cn.zy.domain.Account;
import cn.zy.utils.JdbcUtils_C3P0_v2; public class AccountDao { public void update(Account account) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "update account set name=?,money=? where id=?";
Object params[] = {account.getName(),account.getMoney(),account.getId()};
//JdbcUtils2.getConnection()获取当前线程中的Connection对象
qr.update(JdbcUtils_C3P0_v2.getConnection(), sql, params);
} public Account find(int id) throws SQLException{
QueryRunner qr = new QueryRunner();
String sql = "select * from account where id=?";
//JdbcUtils2.getConnection()获取当前线程中的Connection对象
return (Account) qr.query(JdbcUtils_C3P0_v2.getConnection(),sql, id, new BeanHandler(Account.class));
} }
对AccountService进行改造,service层不再需要传递数据库连接Connection给Dao层,改造后的AccountService如下:
package cn.zy.service; import java.sql.Connection;
import java.sql.SQLException;
import cn.zy.dao.AccountDao;
import cn.zy.domain.Account;
import cn.zy.utils.JdbcUtils_C3P0;
import cn.zy.utils.JdbcUtils_C3P0_v2; public class AccountService {
public void transfer(int sourceid,int targetid,float money) throws SQLException{
Connection conn = null;
try {
//开启事务
JdbcUtils_C3P0_v2.startTransaction();
AccountDao dao = new AccountDao(); Account source = dao.find(sourceid);
Account target = dao.find(targetid); //转账
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
dao.update(source);
dao.update(target);
//提交事务
JdbcUtils_C3P0_v2.commit();
} catch (Exception e) {
e.printStackTrace();
//出现异常之后就回滚事务
JdbcUtils_C3P0_v2.rollback();
}finally{
//关闭数据库连接
JdbcUtils_C3P0_v2.close();
}
} }
这样在service层对事务的处理看起来就更加优雅了。ThreadLocal类在开发中使用得是比较多的,程序运行中产生的数据要想在一个线程范围内共享,只需要把数据使用ThreadLocal进行存储即可。