JavaWeb——(13)DBUtils

目录

一、DBUtils介绍

二、DBUtils核心对象

三、DBUtils使用简单案例

四、QueryRunner对象

4.1构造函数

4.2方法

五、ResultSetHandler接口

六、DBUtils控制事务

6.1ThreadLocal

6.2模拟转账业务

扫描二维码关注公众号,回复: 13161593 查看本文章

一、DBUtils介绍

DBUtils是java编程中的数据库操作实用工具,小巧简单实用,它封装了对JDBC的操作,简化了JDBC操作,主要有以下几点优点

  1. 对于数据表的读操作,他可以把结果转换成List,Array,Set等java集合,便于程序员操作;
  2. 对于数据表的写操作,也变得很简单(只需写sql语句)
  3. 可以使用数据源(数据库连接池)、JNDI等技术来优化性能,重用已经构建好的数据库连接对象

二、DBUtils核心对象

DBUtils有三个核心对象,分别为:

  • QueryRunner类
    • QueryRunner中提供对sql语句操作的三个方法
      • query():用于执行select查询操作
      • update():用于执行insert、update、delete等更新操作
      • batch():批处理,一次执行多个操作
  • ResultSetHandler接口
    • 用于定义select操作后,怎样封装结果集
  • DbUtils类
    • 就是一个工具类,定义了关闭资源与事务处理的方法

三、DBUtils使用简单案例

要使用DBUtils工具包,有以下步骤需要执行:

  1. 首先要导入jar包(提取码:2clq)
  2. 创建QueryRunner对象
  3. 使用query方法执行select语句
  4. 使用ResultSetHandler封装结果集
  5. 使用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是用来封装结果集的,但它是一个接口没有具体实现方法,我们用到的主要有以下九种实现:

  1. ArrayHandler:适合取1条记录,把该条记录的每列值封装到数组Object[]中
  2. ArrayListHandler:适合取多条记录,把每条记录的每列值封装到数组Object[]中,把数组封装到列表List中
  3. ColumnListHandler:取某一列的数据,封装到List中
  4. KeyedHandler:取多条记录,每一条记录封装到一个Map中,再把这个Map封装到另外一个Map中,key为指定的字段值
  5. MapHandler:适合取1条记录,把当前记录的列名和列值放到一个Map中
  6. MapListHandler:适合取多条记录,把每条记录封装到一个Map中,再把Map封装到List中
  7. ScalarHandler:适合取单行单列数据
  8. BeanHandler:适合取1条记录,把当前记录的信息放入对应的实体类中。注意实体类的属性名要和数据表的列名对应。
  9. 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);
    }
}

后台数据库数据变更前后:

猜你喜欢

转载自blog.csdn.net/weixin_39478524/article/details/118408324