目录
一、DBUtils介绍
DBUtils是java编程中的数据库操作实用工具,小巧简单实用,它封装了对JDBC的操作,简化了JDBC操作,主要有以下几点优点
- 对于数据表的读操作,他可以把结果转换成List,Array,Set等java集合,便于程序员操作;
- 对于数据表的写操作,也变得很简单(只需写sql语句)
- 可以使用数据源(数据库连接池)、JNDI等技术来优化性能,重用已经构建好的数据库连接对象
二、DBUtils核心对象
DBUtils有三个核心对象,分别为:
- QueryRunner类
- QueryRunner中提供对sql语句操作的三个方法
- query():用于执行select查询操作
- update():用于执行insert、update、delete等更新操作
- batch():批处理,一次执行多个操作
- QueryRunner中提供对sql语句操作的三个方法
- ResultSetHandler接口
- 用于定义select操作后,怎样封装结果集
- DbUtils类
- 就是一个工具类,定义了关闭资源与事务处理的方法
三、DBUtils使用简单案例
要使用DBUtils工具包,有以下步骤需要执行:
- 首先要导入jar包(提取码:2clq)
- 创建QueryRunner对象
- 使用query方法执行select语句
- 使用ResultSetHandler封装结果集
- 使用DBUtils类释放资源
首先我们导入DBUtils的包,还有C3P0和mysql连接的包用于连接数据库获取连接池,接着通过C3P0获取数据源(连接池)对象,执行query方法获取结果集,然后将结果集中的值赋给实体对象User,最后输出
public void testSelect() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//通过C3P0获取数据源
List<User> list = queryRunner.query("SELECT * FROM employee", new BeanListHandler<User>(User.class));//执行查询,获得结果集并将结果复制到实体类User的列表中
for (User u:list) {
System.out.println(u.toString());
}
}
输出结果如下:
四、QueryRunner对象
QueryRunner中提供对sql语句操作的三个方法,包括query、update和batch,
4.1构造函数
QueryRunner主要有两种构造函数,
QueryRunner();//无参构造,事务可以手动控制。此对象调用的方法(如:query、update、batrch)参数中要有Connection对象
QueryRunner(DataSource ds);//利用数据源进行构造,事务为自动控制的。一个SQL语句为一个事务。此对象调用的方法(如:query、update、batrch)参数中无需Connection对象
4.2方法
<T> T query(String sql, ResultSetHandler<T> rsh, Object... params);//执行一个带有可变参数(参数个数可以为0)的SQL查询
<T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params);//如果使用无参构造,那么这里就要带上Connection对象
int update(String sql, Object... params);//执行一个带有可变参数(参数个数可以为0)的SQL更新,返回更新受影响的行数
int update(Connection conn, String sql, Object... params);//如果使用无参构造,那么这里就要带上Connection对象
int[] batch(String sql, Object[][] params);//执行多个INSERT、UPDATE或DELETE语句。params数组第一维指定了批处理语句的个数,第二维指定了每次处理需要参数的个数
int[] batch(Connection conn, String sql, Object[][] params);//如果使用无参构造,那么这里就要带上Connection对象
我们使用上述QueryRunner对象的方法来实现CRUD操作,
//插入操作
public void testInsert() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//获取QueryRunner对象
int line=queryRunner.update("insert into employee(id,name,gender,salary) values(?,?,?)",4,"Rose","FEMALE",6000);
if (line>0){
System.out.println("数据插入成功!");
}
}
//修改操作
public void testUpdate() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//获取QueryRunner对象
int line=queryRunner.update("update employee set birthday=?,entry_date=?,job=? where id=?",new Date(),new Date(),"CODER",4);
if (line>0){
System.out.println("数据更新成功!");
}
}
//批量处理,只能执行相同的sql语句
public void testBatch() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//获取QueryRunner对象
Object[][] params=new Object[2][];//生成2条语句的参数二维数组
for (int i = 0; i < params.length; i++) {
params[i]=new Object[]{"User"+(i+1),"MALE",(i+1)*100};//对每个语句参数进行赋值
}
queryRunner.batch("insert into employee(name,gender,salary) values(?,?,?)",params);
}
插入结果如图所示:
五、ResultSetHandler接口
ResultSetHandler是用来封装结果集的,但它是一个接口没有具体实现方法,我们用到的主要有以下九种实现:
- ArrayHandler:适合取1条记录,把该条记录的每列值封装到数组Object[]中
- ArrayListHandler:适合取多条记录,把每条记录的每列值封装到数组Object[]中,把数组封装到列表List中
- ColumnListHandler:取某一列的数据,封装到List中
- KeyedHandler:取多条记录,每一条记录封装到一个Map中,再把这个Map封装到另外一个Map中,key为指定的字段值
- MapHandler:适合取1条记录,把当前记录的列名和列值放到一个Map中
- MapListHandler:适合取多条记录,把每条记录封装到一个Map中,再把Map封装到List中
- ScalarHandler:适合取单行单列数据
- BeanHandler:适合取1条记录,把当前记录的信息放入对应的实体类中。注意实体类的属性名要和数据表的列名对应。
- BeanListHandler:适合取多条记录,把每条记录封装到实体类对象中,再把对象封装到List中
下面我们按顺序逐个测试如何使用这几种类来封装结果集:
//ArrayHandler:适合取1条记录,把该条记录的每列值封装到数组Object[]中
public void test1() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
Object[] arr = queryRunner.query("select * from employee where id=?", new ArrayHandler(), 1);
for (Object obj:arr) {
System.out.print(obj+" ");
}
}
//ArrayListHandler:适合取多条记录,把每条记录的每列值封装到数组Object[]中,把数组封装到列表List中
public void test2() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
List<Object[]> list = queryRunner.query("select * from employee", new ArrayListHandler());
for (Object[] objArr:list) {
for (Object obj:objArr) {
System.out.print(obj+" ");
}
System.out.println();
}
}
//ColumnListHandler:取某一列的数据,封装到List中
public void test3() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
List<Object> list = queryRunner.query("select * from employee", new ColumnListHandler(2));//选取结果集中的第二列数据
for (Object obj:list) {
System.out.println(obj);
}
}
//KeyedHandler:取多条记录,每一条记录封装到一个Map中,再把这个Map封装到另外一个Map中,key为指定的字段值
public void test4() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
Map<Object, Map<String, Object>> map = queryRunner.query("select * from employee", new KeyedHandler(1));//选取结果集中的第一列数据作为键,对应着一个存放用户信息的map
for (Map.Entry<Object,Map<String,Object>> m: map.entrySet()) {
for (Map.Entry<String,Object> mm:m.getValue().entrySet()) {
System.out.println(mm.getKey()+":"+mm.getValue());
}
System.out.println("------------------");
}
}
//MapHandler:适合取1条记录,把当前记录的列名和列值放到一个Map中
public void test5() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
Map<String, Object> map = queryRunner.query("select * from employee where id=?", new MapHandler(),3);//选取id为3的用户,并将信息存放到map中
for (Map.Entry<String,Object> m: map.entrySet()) {
System.out.println(m.getKey()+":"+m.getValue());
}
}
//MapListHandler:适合取多条记录,把每条记录封装到一个Map中,再把Map封装到List中
public void test6() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
List<Map<String, Object>> list = queryRunner.query("select * from employee", new MapListHandler());//将结果集每行信息放在map中,再将map放在list中
for (Map<String, Object> map:list) {
for (Map.Entry<String, Object> m:map.entrySet()) {
System.out.println(m.getKey()+":"+m.getValue());
}
System.out.println("------------------");
}
}
//BeanHandler:适合取1条记录,把当前记录的信息放入对应的实体类中
public void test8() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
User user = queryRunner.query("select * from employee where name=?", new BeanHandler<User>(User.class),"Jack");//取单行信息,将结果集中的信息封装到实体类User中
System.out.println(user.toString());
}
//BeanListHandler:适合取多条记录,把每条记录封装到实体类对象中,再把对象封装到List中
public void test9() throws SQLException{
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());
List<User> list = queryRunner.query("select sum(salary) from employee", new BeanListHandler<User>(User.class));//将结果集中的信息封装到实体类User中,并将User放入list中
for (User u:list) {
System.out.println(u.toString());
}
}
六、DBUtils控制事务
6.1ThreadLocal
首先我们介绍一个线程相关的类,ThreadLocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。存储和获取数据就是通过set()和get()方法,调用该类的get方法,只会返回当前线程放入的数据,该数据就是线程的局部变量。
public class TestThreadLocal {
public static void main(String[] args) {
ThreadLocal threadLocal=new ThreadLocal();
threadLocal.set("aaa");
MyThread myThread=new MyThread(threadLocal);
myThread.start();
System.out.println(threadLocal.get());
}
}
class MyThread extends Thread{
private final ThreadLocal threadLocal;
public MyThread(ThreadLocal threadLocal){
this.threadLocal=threadLocal;
}
@Override
public void run() {
System.out.println(threadLocal.get());
}
}
可以看到MyThread类创建的线程就获取不到ThreadLocal创建线程的数据,
ThreadLocal的这种设计就保证了线程的安全,本线程的数据别的线程无法进行访问与修改。
6.2模拟转账业务
当QueryRunner使用无参构造时,就可以手动控制事务,从而在使用ResultSetHandler对象的方法时要传入Connection对象。
我们以转账业务为例,设计一个简单的JavaWeb项目,其设计分层思想如下:
我们将连接作为对象放在ThreadLocal中,然后通过get方法获取到连接对象,并作为参数传入ResultSetHandler的方法中,从而达到控制事务进而达到事务的原子性,这样如果转账的过程中如果发生异常,就不会出现A的账户钱少了,而B的账户钱没有增加的情况。
首先我们新建一个ThreadLocal的管理类,用于获取数据源的连接以及开启事务关闭事务等操作:
import java.sql.Connection;
import java.sql.SQLException;
public class ManageThreadLocal {
private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();
//得到一个连接
public static Connection getConnection(){
Connection connection=threadLocal.get();//从当前线程对象中取出连接
if (connection==null){//若当前线程无连接
connection=C3P0Util.getConnection();//从连接池中取出一个连接
threadLocal.set(connection);//将连接设置到线程局部变量中
}
return connection;
}
//开始事务
public static void startTransaction(){
try {
Connection connection=getConnection();//东当前线程对象中取出连接
connection.setAutoCommit(false);//设置不自动提交事务,手动控制事务
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit(){
try {
Connection connection=getConnection();//东当前线程对象中取出连接
connection.commit();//提交事务
} catch (SQLException e) {
e.printStackTrace();
}
}
//事务回滚
public static void rollback(){
try {
Connection connection=getConnection();//东当前线程对象中取出连接
connection.rollback();//事务回滚
} catch (SQLException e) {
e.printStackTrace();
}
}
//断开连接
public static void close(){
try {
Connection connection=getConnection();//东当前线程对象中取出连接
connection.close();//断开连接,放回连接池中
threadLocal.remove();//将当前线程对象中存放的连接移除
} catch (SQLException e) {
e.printStackTrace();
}
}
}
然后是数据访问层:
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class AccountDaoImpl implements AccountDao {
@Override
public void updateAccount(String payer, String payee, double money) throws SQLException {
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//创建QueryRunner对象
queryRunner.update("update account set money=money-? where name=?",money,payer);//转出
queryRunner.update("update account set money=money+? where name=?",money,payee);//转入
}
@Override
public void updateAccount(Account account) throws SQLException{
QueryRunner queryRunner=new QueryRunner();//无参创建QueryRunner对象
queryRunner.update(ManageThreadLocal.getConnection(),"update account set money=? where name=?",account.getMoney(),account.getName());//根据账户信息修改
}
@Override
public Account findAccountByName(String name) throws SQLException {
QueryRunner queryRunner=new QueryRunner();//无参创建QueryRunner对象
Account account = queryRunner.query(ManageThreadLocal.getConnection(),"select * from account where name=?", new BeanHandler<Account>(Account.class), name);//根据用户名找到用户,并将结果集放在Account对象中
return account;
}
}
interface AccountDao {
/**
* 转账
* @param payer 付款方账户名
* @param payee 收款方账户名
* @param money 转账金额
*/
@Deprecated
public void updateAccount(String payer,String payee,double money)throws SQLException;
/**
* 根据账户信息修改金额
* @param account 修改的账户
*/
public void updateAccount(Account account)throws SQLException;
/**
* 根据用户名查找账户信息
* @param name 用户名
* @return 查找的账户
* @throws Exception
*/
public Account findAccountByName(String name)throws SQLException;
}
然后是业务处理层:
public class AccountServiceImpl implements AccountService {
@Override
public void transfer(String payer, String payee, double money){
AccountDao accountDao=new AccountDaoImpl();
try{
//开始事务
ManageThreadLocal.startTransaction();
//获取付款方和收款方账户
Account payerAcc = accountDao.findAccountByName(payer);
Account payeeAcc = accountDao.findAccountByName(payee);
//修改账户金额
payerAcc.setMoney(payerAcc.getMoney()-money);
payeeAcc.setMoney(payeeAcc.getMoney()+money);
//将账户写入
accountDao.updateAccount(payerAcc);
accountDao.updateAccount(payeeAcc);
//提交事务
ManageThreadLocal.commit();
}catch (Exception e){
ManageThreadLocal.rollback();//回滚事务
}finally {
ManageThreadLocal.close();//关闭连接
}
}
}
interface AccountService {
public void transfer(String payer, String payee, double money);
}
接着我们进行转账测试:
public class TestTransfer {
public static void main(String[] args) throws Exception{
AccountService accountService=new AccountServiceImpl();
accountService.transfer("aaa","bbb",100);
}
}
后台数据库数据变更前后: