JavaWeb学习总结(十四)--Apache的DBUtils
一、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
1 package cn.zy.dbutils; 2 3 import java.util.Date; 4 5 public class User { 6 private int id; 7 private String name; 8 private String password; 9 private String email; 10 private Date birthday; 11 public String getName() { 12 return name; 13 } 14 public void setName(String name) { 15 this.name = name; 16 } 17 public String getPassword() { 18 return password; 19 } 20 public void setPassword(String password) { 21 this.password = password; 22 } 23 @Override 24 public String toString() { 25 return "User [birthday=" + birthday + ", email=" + email + ", id=" + id 26 + ", name=" + name + ", password=" + password + "]"; 27 } 28 public String getEmail() { 29 return email; 30 } 31 public void setEmail(String email) { 32 this.email = email; 33 } 34 public Date getBirthday() { 35 return birthday; 36 } 37 public void setBirthday(Date birthday) { 38 this.birthday = birthday; 39 } 40 public int getId() { 41 return id; 42 } 43 public void setId(int id) { 44 this.id = id; 45 } 46 }
3.建立测试类
1 package cn.zy.test; 2 3 import java.sql.SQLException; 4 import java.util.Date; 5 import java.util.List; 6 7 import org.apache.commons.dbutils.QueryRunner; 8 import org.apache.commons.dbutils.handlers.BeanHandler; 9 import org.apache.commons.dbutils.handlers.BeanListHandler; 10 import org.junit.Test; 11 12 import cn.zy.dbutils.User; 13 import cn.zy.utils.JdbcUtils_C3P0; 14 15 public class QueryRunnerCRUDTest { 16 @Test 17 public void add() throws SQLException{ 18 //将数据源传递给QueryRunner,QueryRunner内部通过数据源获取数据库连接 19 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 20 String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)"; 21 Object param[] = {"张三","123","zhansan@qq.com",new Date(0)}; 22 qr.update(sql, param); 23 } 24 25 @Test 26 public void delete() throws SQLException{ 27 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 28 String sql = "delete from users where id=?"; 29 qr.update(sql,1); 30 } 31 32 @Test 33 public void update() throws SQLException{ 34 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 35 String sql = "update users set name=? where id=?"; 36 Object param[] = {"王五",3}; 37 qr.update(sql, param); 38 } 39 40 @Test 41 public void find() throws SQLException{ 42 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 43 String sql = "select * from users where id=?"; 44 Object params[] = {2}; 45 User user = (User) qr.query(sql, params, new BeanHandler(User.class)); 46 System.out.println(user); 47 } 48 49 @Test 50 public void getAll() throws SQLException{ 51 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 52 String sql = "select * from users"; 53 List list = (List) qr.query(sql, new BeanListHandler(User.class)); 54 System.out.println(list.size()); 55 } 56 57 @Test 58 public void testBatch() throws SQLException{ 59 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 60 String sql = "insert into users(name,password,email,birthday) values(?,?,?,?)"; 61 Object params[][] = new Object[10][]; 62 for (int i=0;i<10;i++){ 63 params[i] = new Object[] { "aa" + i, "123", "aa@sina.com", 64 new Date() }; 65 } 66 qr.batch(sql, params); 67 } 68 }
三、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各种类型的处理器#
1 package cn.zy.test; 2 3 import java.sql.SQLException; 4 import java.util.Arrays; 5 import java.util.List; 6 import java.util.Map; 7 8 import org.apache.commons.dbutils.QueryRunner; 9 import org.apache.commons.dbutils.handlers.ArrayHandler; 10 import org.apache.commons.dbutils.handlers.ArrayListHandler; 11 import org.apache.commons.dbutils.handlers.ColumnListHandler; 12 import org.apache.commons.dbutils.handlers.KeyedHandler; 13 import org.apache.commons.dbutils.handlers.MapHandler; 14 import org.apache.commons.dbutils.handlers.MapListHandler; 15 import org.apache.commons.dbutils.handlers.ScalarHandler; 16 import org.junit.Test; 17 18 import cn.zy.utils.JdbcUtils_C3P0; 19 20 public class ResultSetHandlerTest { 21 @Test 22 public void testArrayHandler() throws SQLException{ 23 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 24 String sql = "select *from users"; 25 Object result[] = (Object[]) qr.query(sql, new ArrayHandler()); 26 System.out.println(Arrays.asList(result)); 27 } 28 29 @Test 30 public void testArrayListHandler() throws SQLException{ 31 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 32 String sql = "select * from users"; 33 List<Object[]> list = (List) qr.query(sql, new ArrayListHandler()); 34 for(Object[] o : list){ 35 System.out.println(Arrays.asList(o)); 36 } 37 } 38 @Test 39 public void testColumnListHandler() throws SQLException{ 40 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 41 String sql = "select * from users"; 42 List list = (List) qr.query(sql, new ColumnListHandler("id")); 43 System.out.println(list); 44 } 45 46 @Test 47 public void testKeyedHandler() throws Exception{ 48 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 49 String sql = "select * from users"; 50 Map<Integer,Map> map = (Map) qr.query(sql, new KeyedHandler("id")); 51 for(Map.Entry<Integer, Map> me : map.entrySet()){ 52 int id = me.getKey(); 53 Map<String,Object> innermap = me.getValue(); 54 for(Map.Entry<String, Object> innerme : innermap.entrySet()){ 55 String columnName = innerme.getKey(); 56 Object value = innerme.getValue(); 57 System.out.println(columnName + "=" + value); 58 } 59 System.out.println("----------------"); 60 } 61 } 62 63 @Test 64 public void testMapHandler() throws SQLException{ 65 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 66 String sql = "select * from users"; 67 Map<String,Object> map = (Map) qr.query(sql, new MapHandler()); 68 for(Map.Entry<String, Object> me : map.entrySet()){ 69 System.out.println(me.getKey() + "=" + me.getValue()); 70 } 71 } 72 73 @Test 74 public void testMapListHandler() throws SQLException{ 75 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 76 String sql = "select * from users"; 77 List<Map> list = (List) qr.query(sql, new MapListHandler()); 78 for(Map<String,Object> map :list){ 79 for(Map.Entry<String, Object> me : map.entrySet()) 80 System.out.println(me.getKey() + "=" + me.getValue()); 81 } 82 } 83 84 @Test 85 public void testScalarHandler() throws SQLException{ 86 QueryRunner qr = new QueryRunner(JdbcUtils_C3P0.getDataSource()); 87 String sql = "select count(*) from users"; 88 int count = ((Long)qr.query(sql, new ScalarHandler(1))).intValue(); 89 System.out.println(count); 90 } 91 92 }
依次返回:
====================================
====================================
====================================
+++++++++++++++++++++++++++++++++++
===================================
++++++++++++++++++++++++++++++++++
三、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层
1 package cn.zy.domain; 2 3 import java.util.Date; 4 5 public class Account { 6 private int id; 7 private String name; 8 private Float money; 9 public int getId() { 10 return id; 11 } 12 public void setId(int id) { 13 this.id = id; 14 } 15 public String getName() { 16 return name; 17 } 18 @Override 19 public String toString() { 20 return "Account [id=" + id + ", money=" + money + ", name=" + name 21 + "]"; 22 } 23 public void setName(String name) { 24 this.name = name; 25 } 26 public Float getMoney() { 27 return money; 28 } 29 public void setMoney(Float money) { 30 this.money = money; 31 } 32 public Account(int id, String name, Float money) { 33 super(); 34 this.id = id; 35 this.name = name; 36 this.money = money; 37 } 38 public Account() { 39 super(); 40 } 41 42 }
写一个Dao类进行进行CURD操作
1 package cn.zy.dao; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.apache.commons.dbutils.handlers.BeanHandler; 7 import cn.zy.domain.Account; 8 9 public class AccountDao { 10 11 //接受service层传递过来的Connection对象 12 private Connection conn = null; 13 14 public AccountDao(Connection conn){ 15 this.conn = conn; 16 } 17 public AccountDao(){ 18 19 } 20 21 public void update(Account account) throws SQLException{ 22 QueryRunner qr = new QueryRunner(); 23 String sql = "update account set name=?,money=? where id=?"; 24 Object params[] = {account.getName(),account.getMoney(),account.getId()}; 25 26 //使用service层传递过来的Connection对象操作数据库 27 qr.update(conn, sql, params); 28 System.out.println("更新成功"); 29 } 30 31 public Account find(int id) throws SQLException{ 32 QueryRunner qr = new QueryRunner(); 33 String sql = "select * from account where id=?"; 34 //使用service层传递过来的Connection对象操作数据库 35 return (Account) qr.query(conn,sql, id, new BeanHandler(Account.class)); 36 37 38 } 39 40 }
在业务层实现转账
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(); } } }
测试:
1 package cn.zy.test; 2 3 import java.sql.SQLException; 4 import org.junit.Test; 5 import cn.zy.service.AccountService; 6 7 public class AccountServiceTest { 8 @Test 9 public void fun() throws SQLException{ 10 AccountService service = new AccountService(); 11 service.transfer(1, 2, 100f); 12 } 13 }
4.2、使用ThreadLocal进行更加优雅的事务处理#
上面的在businessService层这种处理事务的方式依然不够优雅,为了能够让事务处理更加优雅,我们使用ThreadLocal类进行改造,ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了
ThreadLocal类的使用范例如下:
1 package me.gacl.test; 2 3 public class ThreadLocalTest { 4 5 public static void main(String[] args) { 6 //得到程序运行时的当前线程 7 Thread currentThread = Thread.currentThread(); 8 System.out.println(currentThread); 9 //ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来 10 ThreadLocal<String> t = new ThreadLocal<String>(); 11 //把某个对象绑定到当前线程上 对象以键值对的形式存储到一个Map集合中,对象的的key是当前的线程,如: map(currentThread,"aaa") 12 t.set("aaa"); 13 //获取绑定到当前线程中的对象 14 String value = t.get(); 15 //输出value的值是aaa 16 System.out.println(value); 17 } 18 }
使用使用ThreadLocal类进行改造数据库连接工具类JdbcUtils,改造后的代码如下:
1 package cn.zy.utils; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import javax.sql.DataSource; 6 import com.mchange.v2.c3p0.ComboPooledDataSource; 7 8 public class JdbcUtils_C3P0_v2 { 9 private static ComboPooledDataSource ds = null; 10 //使用ThreadLoacal存储当前线程中的Connection对象 11 private static ThreadLocal<Connection> threadLocal =new ThreadLocal<Connection>(); 12 13 //在静态块中创建连接池 14 static{ 15 try { 16 //使用C3P0的命名配置来创建数据源 17 ds = new ComboPooledDataSource("MySQL"); 18 } catch (Exception e) { 19 throw new ExceptionInInitializerError(e); 20 } 21 } 22 23 /* 24 * 从数据源中获取数据库连接 25 */ 26 public static Connection getConnection() throws SQLException{ 27 //从当前线程中获取数据库连接 28 Connection conn = threadLocal.get(); 29 if(conn==null){ 30 //从数据源中获取连接 31 conn = ds.getConnection(); 32 //将conn绑定到当前线程 33 threadLocal.set(conn); 34 } 35 return conn; 36 } 37 38 /* 39 * 开启事务 40 */ 41 public static void startTransaction(){ 42 try { 43 Connection conn = threadLocal.get(); 44 if(conn==null){ 45 conn = getConnection(); 46 //把conn绑定到当前线程上 47 threadLocal.set(conn); 48 } 49 //开启事务 50 conn.setAutoCommit(false); 51 } catch (Exception e) { 52 throw new RuntimeException(e); 53 } 54 } 55 56 /* 57 * 回滚事务 58 */ 59 public static void rollback(){ 60 try { 61 Connection conn = threadLocal.get(); 62 if(conn!=null){ 63 //回滚事务 64 conn.rollback(); 65 } 66 } catch (Exception e) { 67 throw new RuntimeException(e); 68 } 69 } 70 71 /* 72 * 提交事务 73 */ 74 public static void commit(){ 75 try { 76 Connection conn = threadLocal.get(); 77 if(conn!=null){ 78 //提交事务 79 conn.commit(); 80 } 81 } catch (Exception e) { 82 throw new RuntimeException(e); 83 } 84 } 85 86 /* 87 * 关闭数据库连接 88 */ 89 public static void close(){ 90 try { 91 //从当前线程获取连接 92 Connection conn = threadLocal.get(); 93 if(conn!=null){ 94 conn.close(); 95 //从当前线程接解除定 96 threadLocal.remove(); 97 } 98 } catch (Exception e) { 99 throw new RuntimeException(e); 100 } 101 } 102 103 /* 104 * 获取数据源 105 */ 106 public static DataSource getDataSource(){ 107 return ds; 108 } 109 }
对AccountDao进行改造,数据库连接对象不再需要service层传递过来,而是直接从JdbcUtils2提供的getConnection方法去获取,改造后的AccountDao如下:
1 package cn.zy.dao; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import org.apache.commons.dbutils.QueryRunner; 6 import org.apache.commons.dbutils.handlers.BeanHandler; 7 import cn.zy.domain.Account; 8 import cn.zy.utils.JdbcUtils_C3P0_v2; 9 10 public class AccountDao { 11 12 public void update(Account account) throws SQLException{ 13 QueryRunner qr = new QueryRunner(); 14 String sql = "update account set name=?,money=? where id=?"; 15 Object params[] = {account.getName(),account.getMoney(),account.getId()}; 16 //JdbcUtils2.getConnection()获取当前线程中的Connection对象 17 qr.update(JdbcUtils_C3P0_v2.getConnection(), sql, params); 18 } 19 20 public Account find(int id) throws SQLException{ 21 QueryRunner qr = new QueryRunner(); 22 String sql = "select * from account where id=?"; 23 //JdbcUtils2.getConnection()获取当前线程中的Connection对象 24 return (Account) qr.query(JdbcUtils_C3P0_v2.getConnection(),sql, id, new BeanHandler(Account.class)); 25 } 26 27 }
对AccountService进行改造,service层不再需要传递数据库连接Connection给Dao层,改造后的AccountService如下:
1 package cn.zy.service; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import cn.zy.dao.AccountDao; 6 import cn.zy.domain.Account; 7 import cn.zy.utils.JdbcUtils_C3P0; 8 import cn.zy.utils.JdbcUtils_C3P0_v2; 9 10 public class AccountService { 11 public void transfer(int sourceid,int targetid,float money) throws SQLException{ 12 Connection conn = null; 13 try { 14 //开启事务 15 JdbcUtils_C3P0_v2.startTransaction(); 16 AccountDao dao = new AccountDao(); 17 18 Account source = dao.find(sourceid); 19 Account target = dao.find(targetid); 20 21 //转账 22 source.setMoney(source.getMoney()-money); 23 target.setMoney(target.getMoney()+money); 24 dao.update(source); 25 dao.update(target); 26 //提交事务 27 JdbcUtils_C3P0_v2.commit(); 28 } catch (Exception e) { 29 e.printStackTrace(); 30 //出现异常之后就回滚事务 31 JdbcUtils_C3P0_v2.rollback(); 32 }finally{ 33 //关闭数据库连接 34 JdbcUtils_C3P0_v2.close(); 35 } 36 } 37 38 }
这样在service层对事务的处理看起来就更加优雅了。ThreadLocal类在开发中使用得是比较多的,程序运行中产生的数据要想在一个线程范围内共享,只需要把数据使用ThreadLocal进行存储即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
2015-12-02 Apache与WebLogic 的整合