CT的JBDC学习

本文详细介绍了Java JDBC与数据库的交互,涵盖了JDBC概述、数据持久化、JDBC API、连接数据库的多种方式、PreparedStatement的使用、批量操作、数据库事务的ACID属性、DAO设计模式及其实现类,以及C3P0、DBCP、Druid等数据库连接池的使用。此外,还讨论了Apache-DBUtils在简化CRUD操作中的应用,以及如何自定义ResultSetHandler。最后,文章提到了资源关闭的Dbutils工具类方法。

1 Web技术概览

在这里插入图片描述

2 JDBC概述

2.1 数据持久化

把内存数据长久化保存。

2.2 Java中的数据存储

  • JDBC直接访问
  • JDO技术
  • 第三方O/R工具,如Hibernate、Mybatis等

2.3 JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口。定义了用于访问数据库的标准Java类库(java.sql javax.sql)使用这些类库可以以一种标准方法方便地访问数据库资源
  • 统一的规范
  • 目的是为使用JDBC连接任何提供了JDBC驱动程序的数据库,简化流程
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQNZtEO5-1645449806326)(C:\Users\Tao\AppData\Roaming\Typora\typora-user-images\image-20211229103928714.png)]

2.4 JDBC体系结构

  • 面向应用的API:程序员开发使用
  • 面向数据库的API:供厂商开发驱动

2.5 JDBC编写步骤

在这里插入图片描述

3 数据库连接方式

Maven项目中首先要导入mysql-connector-java依赖。

3.1 方式一

public class ConnectionTest {
    @Test
    public void testConnection1() throws SQLException {
        Driver driver = new com.mysql.cj.jdbc.Driver();

        String url = "jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8";
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","");

        Connection conn = driver.connect(url, info);
        System.out.println(conn);
    }
}

jdbc:mysql:协议

localhost:ip地址

3306:mysql默认端口号

jdbc_learn:数据库名

?useUnicode=true&characterEncoding=utf-8:东八区添加

3.2 方式二-反射获取driver对象

Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();

与方式一的区别是在第一步获取driver对象时使用反射。

这样具有可移植性。

3.3 方式三-使用DriverManager

public void testConnection3() throws Exception {
    Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
    Driver driver = (Driver) clazz.newInstance();

    //2.提供基本信息
    String url="jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8";
    String user = "root";
    String password = "";

    //注册驱动
    DriverManager.registerDriver(driver);

    //获取连接
    DriverManager.getConnection(url,user,password);
}

3.4 方式四-省略注册驱动

    public void testConnection4() throws Exception {
        //提供基本信息
        String url="jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8";
        String user = "root";
        String password = "";

        //加载Driver
        Class.forName("com.mysql.cj.jdbc.Driver");
//        Driver driver = (Driver) clazz.newInstance();
//        //注册驱动
//        DriverManager.registerDriver(driver);

        //获取连接
        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);
    }

与方式三的区别是省略了注册驱动的代码。

原因:mysql的driver类中写了注册驱动的静态方法,随着driver类的加载自动注册了驱动。

3.5 方式五-最终版

将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接。

#连接数据库的四个基本信息
user=root
password=
url=jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8
driverClass=com.mysql.cj.jdbc.Driver
public void testConnection5() throws Exception {

    InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");

    Properties pros = new Properties();
    pros.load(is);

    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String url = pros.getProperty("url");
    String driverClass = pros.getProperty("driverClass");

    Class.forName(driverClass);

    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn);

}

优点:

  1. 实现代码与数据的分离,实现了解耦。
  2. 如果需要修改配置文件信息,可以避免重新打包。

3 使用PreparedStatement实现CRUD操作

3.1 操作和访问数据库

  • 数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个socket连接。

  • 在java.sql包中有3个接口分别定义了对数据库调用的不同方式:

    • Statement:用于执行静态SQL语句并返回它所生成结果的对象
    • PreparedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效的执行该语句
    • CallableStatement:用于执行SQL存储过程
      在这里插入图片描述

3.2 使用Statement的弊端

  • 需要sql语句拼串,操作繁琐
  • 且存在SQL注入问题:可以输入错误的用户名和密码但是正确连接数据库

3.3 PreparedStatement的使用

3.3.1 介绍

3.3.2 JDBCUtils:封装数据库连接和连接关闭

public class JDBCUtils {
    /**
    * @Description: 获取数据库连接
    * @Param: []
    * @return: java.sql.Connection
    * @Author: CT
    * @Date: 2021/12/29
    */
    public static Connection getConnection() throws Exception {
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");

        Properties pros = new Properties();
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

        Class.forName(driverClass);

        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }
    /**
    * @Description: 关闭连接和Statement的操作
    * @Param: [conn, ps]
    * @return: void
    * @Author: CT
    * @Date: 2021/12/29
    */
    public static void closeResource(Connection conn, Statement ps){
        try {
            if(ps!=null)
                ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn!=null)
                conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

3.3.3 增改

public void testInsert() {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");

            Properties pros = new Properties();
            pros.load(is);

            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");

            Class.forName(driverClass);

            conn = DriverManager.getConnection(url, user, password);
//        System.out.println(conn);

            //4.预编译sql语句,返回PreparedStatement实例
            String sql = "insert into customers(name,email,birth)value(?,?,?)";
            ps = conn.prepareStatement(sql);
            //5.填充占位符
            ps.setString(1,"哪吒");
            ps.setString(2,"nezha@gmail.com");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = sdf.parse("1000-01-01");
            ps.setDate(3,new Date(date.getTime()));

            //6.执行操作
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //7.连接关闭
            try {
                if(ps!=null)
                    ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(conn!=null)
                    conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
public void testUpdate() {
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        conn = JDBCUtils.getConnection();

        String sql = "update customers set name = ? where id = ?";
        ps = conn.prepareStatement(sql);

        ps.setObject(1,"莫扎特");
        ps.setObject(2,18);

        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        JDBCUtils.closeResource(conn,ps);
    }
}

3.3.4 实现通用的增删改

通用方法

public  void update(String sql,Object ...args){
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        //1.获取连接
        conn = JDBCUtils.getConnection();
        //2.预编译sql语句
        ps = conn.prepareStatement(sql);
        //3.填充占位符
        for(int i = 0;i<args.length;i++){
            ps.setObject(i+1,args[i]);
        }
        //4.执行
        ps.execute();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //5.关闭资源
        JDBCUtils.closeResource(conn,ps);
    }
}

测试

public void testCommonUpdate(){
    String sql = "delete from customers where id = ?";
    update(sql,3);
}

3.3.5 针对单个表的具体查询

public void testQuery1(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet resultSet = null;
    try {
        conn = JDBCUtils.getConnection();

        String sql = "select id,name,email,birth from `customers` where id = ?";
        ps = conn.prepareStatement(sql);
        ps.setObject(1,1);


        resultSet = ps.executeQuery();

        if(resultSet.next()){
            //获取当前数据的各个字段
            int id = resultSet.getInt(1);
            String name = resultSet.getString(2);
            String email = resultSet.getString(3);
            Date birth = resultSet.getDate(4);
            //将数据封装为一个对象
            Customer customer = new Customer(id, name, email, birth);
            System.out.println(customer);

        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,ps,resultSet);
    }
}

3.3.6 针对单个表的通用查询

public Customer queryForCustomers(String sql,Object ...args){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = JDBCUtils.getConnection();

        ps = conn.prepareStatement(sql);
        //填充占位符
        for(int i = 0 ; i < args.length;i++){
            ps.setObject(i+1,args[i]);
        }

        rs = ps.executeQuery();
        //获取元数据
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        if(rs.next()){
            Customer customer = new Customer();
            for(int i=0;i<columnCount;i++){
                Object columnValue = rs.getObject(i + 1);

                //获取每个列的列名
                String columnName = rsmd.getColumnName(i+1);

                //给customer对象指定的columnName属性,赋值为columnValue,反射
                Field field = Customer.class.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(customer,columnValue);
            }
            return customer;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,ps,rs);
    }
    return null;
}

测试用例

public void testQueryForCustomer(){
    String sql = "select id,name,email,birth from `customers` where id = ?";
    Customer customer = queryForCustomers(sql, 13);
    System.out.println(customer);
}

3.3.7 表的字段名和类的属性名不一致

表的字段名和类的属性名不一致时,需要通过为结果集起别名的方式获得结果。别名与类的属性名相同。

此时将

String columnName = rsmd.getColumnName(i+1);//替换为以下方法
String columnName = rsmd.getColumnLabel(i+1);
public Order orderForQuery(String sql,Object ...args){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = JDBCUtils.getConnection();
        ps = conn.prepareStatement(sql);
        for(int i = 0;i<args.length;i++){
            ps.setObject(i+1,args[i]);
        }

        rs = ps.executeQuery();
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        if(rs.next()){
            Order order = new Order();
            for(int i = 0;i<columnCount;i++){
                Object columnValue = rs.getObject(i + 1);
                String columnName = rsmd.getColumnLabel(i+1);

                Field field = Order.class.getDeclaredField(columnName);
                field.setAccessible(true);
                field.set(order,columnValue);
            }
            return order;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
    JDBCUtils.closeResource(conn,ps,rs);
    }
    return null;
}

测试用例

public void testOrderForQuery(){
    String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
    Order order = orderForQuery(sql, 1);
    System.out.println(order);
}

3.3.8 针对不同表的通用查询

public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = JDBCUtils.getConnection();

        ps = conn.prepareStatement(sql);
        //填充占位符
        for(int i = 0 ; i < args.length;i++){
            ps.setObject(i+1,args[i]);
        }

        rs = ps.executeQuery();
        //获取元数据
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        ArrayList<T> list = new ArrayList<T>();
        while(rs.next()){
            T t = clazz.newInstance();
            for(int i=0;i<columnCount;i++){
                Object columnValue = rs.getObject(i + 1);

                //获取每个列的列名
                String columnLabel = rsmd.getColumnLabel(i+1);

                //给t对象指定的columnName属性,赋值为columnValue,反射
                Field field = clazz.getDeclaredField(columnLabel);
                field.setAccessible(true);
                field.set(t,columnValue);
            }
            list.add(t);
        }
        return list;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,ps,rs);
    }
    return null;
}

测试用例

public void testGetForList(){
    String sql="select id,name,email,birth from `customers` where id < ?";
    List<Customer> list = getForList(Customer.class, sql, 12);
    list.forEach(System.out::println);
}

3.3.9 PreparedStatement的优点

  1. 解决了拼串问题
  2. PreparedStatement使用了sql语句的预编译,从而使sql语句不会被语句陷阱修改原逻辑关系,解决了SQL注入问题
  3. PreparedStatement可以操作Blob的数据,而Statement不行
  4. 可以实现更高效的批量操作

3.4 Statement vs PreparedStatement

  • 代码的可读性和可维护性
  • PreparedStatement能最大可能提高性能
    • DBServer会对预编译语句提供性能优化。编译过的代码会缓存,后续只需要传参
    • Statement没有缓存,所以没传一次语句需要重新编译一次
    • (语法检查、语义检查、翻译成二进制命令,缓存)
  • PreparedStatement可以防止SQL注入

3.5 小结

  • 两种思想
    • 面向接口编程思想
    • ORM思想(object relational mapping)
      • 一个数据表对应一个Java类
      • 表中一条记录对应Java类的一个记录
      • 表中一个字段对应Java类的属性

sql是需要结合列名和表的属性名来写,注意起别名

  • 两种技术
    • JDBC结果集的元数据:ResultSetMetaData
      • 获取列数:getColumnCount()
      • 获取列的别名:getColumnLabel()
    • 通过反射,创建指定类的对象,获取指定的属性并赋值。

4 操作BLOB类型字段

4.1 MySQL BLOB类型

  • MySQL中,BLOB是一个二进制的大型对象,是一个可以储存大量数据的容器,它能容纳不同大小的数据。

  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)。
    在这里插入图片描述

  • 根据存入数据大小定义不同的BLOB类型。

  • 需要注意的是:如果存储的文件过大,数据库的性能会下降。

  • 如果在指定了相关的Blob类型后还报错:xxx too large,那么在mysql安装目录下找到my.ini文件加上如下配置:

    max_allowed_packet=16M
    

    需要重启mysql服务。

4.2 向数据表插入BLOB类型数据

//向数据表customers中插入Blob型字段
@Test
public void testInsert() throws Exception {
    Connection conn = JDBCUtils.getConnection();
    String sql="insert into customers(name,email,birth,photo)values(?,?,?,?)";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setObject(1,"陈涛");
    ps.setObject(2,"ct@qq.com");
    ps.setObject(3,"1999-05-18");
    FileInputStream is = new FileInputStream(new File("src/main/java/com/images/timg.jpg"));
    ps.setObject(4,is);

    ps.execute();
    JDBCUtils.closeResource(conn,ps);
}

4.3 查询BLOB字段

BLOB字段采用IO流获取

@Test
public void testQuery(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    InputStream is = null;
    FileOutputStream fos = null;
    try {
        conn = JDBCUtils.getConnection();
        String sql="select id,name,email,birth,photo from customers where id = ?";
        ps = conn.prepareStatement(sql);
        ps.setObject(1,22);

        rs = ps.executeQuery();
        if(rs.next()){
            int id = rs.getInt("id");
            String name = rs.getString("name");
            String email = rs.getString("email");
            Date birth = rs.getDate("birth");

            Customer customer = new Customer(id, name, email, birth);
            System.out.println(customer);

            Blob photo = rs.getBlob("photo");
            is = photo.getBinaryStream();
            fos= new FileOutputStream("sds.jpg");
            byte[] buffer = new byte[1024];
            int len;
            while((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if(is!=null)
                is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if(fos!=null)
                fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        JDBCUtils.closeResource(conn,ps,rs);
    }
}

5 批量插入

5.1 层次一:使用Statement

5.2 层次二:使用PreparedStatement

时间:52s

//批量插入方式二:使用PreparedStatement
@Test
public void testInsert(){
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        long start = System.currentTimeMillis();
        conn = JDBCUtils.getConnection();
        String sql="insert into goods(name)values(?)";
        ps = conn.prepareStatement(sql);
        for(int i = 1;i<=20000;i++){
            ps.setObject(1,"name_"+i);
            ps.execute();
        }
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));//time:52711
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,ps);
    }
}

5.3 层次三:batch相关方法

时间:1488

/*批量插入方式三:
* 1.addBatch()、executeBatch()、clearBatch()
*
* */
@Test
public void testInsert2(){
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        long start = System.currentTimeMillis();
        conn = JDBCUtils.getConnection();
        String sql="insert into goods(name)values(?)";
        ps = conn.prepareStatement(sql);
        for(int i = 1;i<=20000;i++){
            ps.setObject(1,"name_"+i);
            //1.攒sql
            ps.addBatch();
            if(i % 500 == 0){
                //2.运行batch
                ps.executeBatch();
                //3.清空batch
                ps.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("time:"+(end-start));//time:52711
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,ps);
    }
}

Tips:

mysql服务器默认关闭批处理,我们需要改参数,让mysql开启批处理支持,

rewriteBatchedStatements=true

写在配置文件的url后面

mysql8.0默认支持

5.4 层次四:设置不允许自动提交

时间:1399

在层次三的基础上,对Connection的自动提交设置为false,在预编译结束后统一提交。

conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
...
...
conn.commit();

6 数据库事务

6.1 数据库事务介绍

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态
  • 事务处理:保证所有事务作为一个工作单元来执行,即使出现故障也不会改变这种执行方式。当一个事务执行多个操作时,要么全部提交(commit),全部修改保留下来;要么放弃所有的更改,整个事务**回滚(rollback)**到最初状态。
  • 为保证数据一致性

6.2 JDBC事务处理

首先明确一个关键点:数据一旦提交,就不可回滚

而哪些操作会导致数据自动提交?

  • DDL操作一旦执行,都会自动提交
  • DML操作默认执行后会提交
    • 可以通过set autocommit = false方式取消DML操作的自动提交
  • 默认在关闭连接时自动提交数据

JDBC程序为了让多个SQL语句作为一个事务执行,提供了:

  • Connection对象的setAutoCommit(false)以取消自动提交事务
  • 在所有SQL语句执行成功后,调用commit()方法统一提交事务
  • 在出现异常时,调用rollback()方法回滚事务

若此时Connection未关闭,还可能被重复使用,则需要回复其自动提交状态:setAutoCommit(true)

尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交

【实例:AA向BB转账100】

    //****************************考虑数据库事务
    @Test
    public void testUpdateWithTx(){
        Connection conn = null;
        try {
            //将连接放到事务操作的开始事务中关闭连接
            conn = JDBCUtils.getConnection();
            //1.取消数据自动提交
            conn.setAutoCommit(false);

            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            update(conn,sql1,"AA");

//            //模拟异常
//            System.out.println(10/0);

            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            update(conn,sql2,"BB");

            System.out.println("转账成功");

            //2.最后提交数据
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //3.回滚操作
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }
    //通用的增删改--version 2.0(应用于事务)
    public  int update(Connection conn,String sql,Object ...args){
        PreparedStatement ps = null;
        try {
            //1.预编译sql语句
            ps = conn.prepareStatement(sql);
            //2.填充占位符
            for(int i = 0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }
            //3.执行
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

6.3 事务的ACID属性

  1. 原子性(Atomicity)

    事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)

    事务必须使数据库从一个一致性状态变换到另外一个一致性状态

    即:事务按照预期生效,数据的状态是预期的状态。

  3. 隔离性(Isolation)

    事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

  4. 持久性(Durability)

    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

6.3.1 数据库并发问题

  • 对于同时运行的多个事务,当这些事务访问相同数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:
    1. 脏读(dirty read): A事务读取了B事务尚未提交的更改数据,并且在这个数据基础上进行操作。如果此时恰巧B事务进行回滚,那么A事务读到的数据是根本不被承认的。
    2. 不可重复读(unrepeatable read):不可重复读是指A事务读取了B事务已经提交的更改数据。假设A在取款事务的过程中,B往该账户转账100元,A两次读取账户的余额发生不一致。字段
    3. 幻读(phantom read): A事务读取B提交的新增数据,这时A事务将出现幻读的问题。幻读一般发生在计算统计数据的事务中。举个例子,假设银行系统在同一个事务中两次统计存款的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时两次统计的总金额将不一致。
  • 数据库事务的隔离性:数据库系统必须具有隔离并并发运行各个事务的能力,使他们不会互相影响,避免并发问题
  • 一个事务与其他事务隔离的程度称为隔离级别。隔离级别越高,数据一致性就越好,并发性就越弱

6.3.2 四种隔离级别

image-20220110232458032
  • Oracle支持2种事务隔离级别:READ COMMITTED,SERIALIZABLE。Oracle默认事务隔离级别为:READ COMMITTED
  • Mysql支持4种事务隔离级别,默认为:REPEATABLE READ

6.3.3 Mysql中设置隔离级别

  • 每个mysql数据库连接都有一个全局变量@tx_isolation,表示事务隔离级别
  • 查看
SELECT @@tx_isolation;
  • 设置当前数据库连接的隔离级别
set transaction isolation level read committed;
  • 设置全局
set global transaction isolation level read committed;
  • 补充

    • 创建用户
    create user 用户名 identified by '密码';
    
    • 授予权限
    #赋予用户对表的增删改查的权限
    grant select,insert,update,delete on 数据库.* to 用户名@localhost identified by '密码';
    

7 DAO及其实现类

  • DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道,夹在业务逻辑与数据库资源中间。包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息

  • 作用:为了实现功能的模块化,更利于代码的维护和升级

  • 流程:

​ BaseDAO写出通用的方法——>针对特定表的DAO接口,继承了BaseDAO,规范一系列针对特定表的操作——>特定表接口 DAO的实现类DAOImpl里面,编写具体的方法

【BaseDAO】类

/**
 * @author CT
 * @Description 封装了针对于数据表的通用操作
 * @create 2022-01-12-22:19
 */
public abstract class BaseDAO {
    //通用的增删改--v.2.0(应用于事务)
    public  int update(Connection conn, String sql, Object ...args){
        PreparedStatement ps = null;
        try {
            //1.预编译sql语句
            ps = conn.prepareStatement(sql);
            //2.填充占位符
            for(int i = 0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }
            //3.执行
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.关闭资源
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

    //通用查询操作:返回数据表一条数据--v.2.0(事务)
    public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {

            ps = conn.prepareStatement(sql);
            //填充占位符
            for(int i = 0 ; i < args.length;i++){
                ps.setObject(i+1,args[i]);
            }

            rs = ps.executeQuery();
            //获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if(rs.next()){
                T t = clazz.newInstance();
                for(int i=0;i<columnCount;i++){
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名
                    String columnLabel = rsmd.getColumnLabel(i+1);

                    //给customer对象指定的columnName属性,赋值为columnValue,反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }

    //查询返回多条数据--v.2.0(事务)
    public <T> List<T> getForList(Connection conn,Class<T> clazz, String sql, Object ...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            //填充占位符
            for(int i = 0 ; i < args.length;i++){
                ps.setObject(i+1,args[i]);
            }

            rs = ps.executeQuery();
            //获取元数据
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            ArrayList<T> list = new ArrayList<T>();
            while(rs.next()){
                T t = clazz.newInstance();
                for(int i=0;i<columnCount;i++){
                    Object columnValue = rs.getObject(i + 1);

                    //获取每个列的列名
                    String columnLabel = rsmd.getColumnLabel(i+1);

                    //给t对象指定的columnName属性,赋值为columnValue,反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
    //查询特殊值的通用方法
    public <E> E getValue(Connection conn,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for(int i = 0;i<args.length;i++){
                ps.setObject(i+1,args[i]);
            }
            rs = ps.executeQuery();
            if(rs.next()){
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
}

【CustomerDAO】类

/**
 * @author CT
 * @Description 规范针对于customers表的常用操作
 * @create 2022-01-12-22:32
 */
public interface CustomerDAO {
    /**
    * @Description: 将customer对象添加到数据库中
    * @Param: [conn, customer]
    * @return: void
    * @Author: CT
    * @Date: 2022/1/12 22:37
    */
    void insert(Connection conn, Customer customer);
    /**
    * @Description: 根据id删除表中一条记录
    * @Param: [conn, id]
    * @return: void
    * @Author: CT
    * @Date: 2022/1/12 22:40
    */
    void deleteByID(Connection conn,int id);
    /**
    * @Description: 根据内存中的customer对象修改表中对应的记录
    * @Param: [conn, customer]
    * @return: void
    * @Author: CT
    * @Date: 2022/1/12 22:41
    */
    void update(Connection conn,Customer customer);
    /**
    * @Description: 根据id查询Customer对象
    * @Param: [conn, id]
    * @return: com.ct2.bean.Customer
    * @Author: CT
    * @Date: 2022/1/12 22:42
    */
    Customer getCustomerById(Connection conn,int id);
    /**
    * @Description: 查询数据表中的所有记录,返回集合
    * @Param: [conn]
    * @return: java.util.List<com.ct2.bean.Customer>
    * @Author: CT
    * @Date: 2022/1/12 22:44
    */
    List<Customer> getAll(Connection conn);
    /**
    * @Description: 获取数据表中记录数
    * @Param: [conn]
    * @return: long
    * @Author: CT
    * @Date: 2022/1/12 22:45
    */
    long getCount(Connection conn);
    /**
    * @Description: 返回数据表最大的生日
    * @Param: [conn]
    * @return: java.sql.Date
    * @Author: CT
    * @Date: 2022/1/12 22:46
    */
    Date getMaxBirth(Connection conn);
}

【CustomerDAOImpl】类

/**
 * @author CT
 * @Description Customer表DAO的实现类
 * @create 2022-01-12-22:47
 */
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
    @Override
    public void insert(Connection conn, Customer customer) {
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth());
    }

    @Override
    public void deleteByID(Connection conn, int id) {
        String sql = "delete from customers where id = ?";
        update(conn,sql,id);
    }

    @Override
    public void update(Connection conn, Customer customer) {
        String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
        update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth(),customer.getId());
    }

    @Override
    public Customer getCustomerById(Connection conn, int id) {
        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = getInstance(conn, Customer.class, sql, id);
        return customer;
    }

    @Override
    public List<Customer> getAll(Connection conn) {
        String sql ="select id,name,email,birth from customers";
        List<Customer> list = getForList(conn, Customer.class, sql);
        return list;
    }

    @Override
    public long getCount(Connection conn) {
        String sql = "select count(*) from customers";
        return getValue(conn, sql);
    }

    @Override
    public Date getMaxBirth(Connection conn) {
        String sql = "select max(birth) from customers";
        return getValue(conn,sql);
    }
}

测试用例

/**
 * @author CT
 * @Description 测试DAO及其实现类功能
 * @create 2022-01-12-23:09
 */
public class CustomerDAOImplTest {
    private CustomerDAOImpl dao = new CustomerDAOImpl();

    @Test
    public void insert() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(1, "ct", "1827323@qq.com", new Date(972323213L));
            dao.insert(conn,customer);
            System.out.println("添加成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void deleteByID() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();

            dao.deleteByID(conn,23);

            System.out.println("删除成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void update() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Customer customer = new Customer(22, "ct", "1827323@qq.com", new Date(972323213L));
            dao.update(conn,customer);
            System.out.println("修改成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void getCustomerById() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();

            Customer customer = dao.getCustomerById(conn, 10);
            System.out.println(customer);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void getAll() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            List<Customer> list = dao.getAll(conn);
            for(Customer c : list){
                System.out.println(c);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void getCount() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            System.out.println("记录数:"+dao.getCount(conn));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }

    @Test
    public void getMaxBirth() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            Date maxBirth = dao.getMaxBirth(conn);
            System.out.println("最大的生日(数值上):"+maxBirth);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(conn,null);
        }
    }
}

8 数据库连接池

8.1 JDBC数据库连接池的必要性

  • 开发基于数据库的web程序时,传统模式基本是按以下步骤:
    • 在主程序(如servlet、beans)中建立数据库连接
    • 进行sql连接
    • 断开数据库连接
  • 传统模式存在以下问题:
    • 普通数据库连接使用DriverManager来获取,每次建立连接都需要将Connection加载到内存中,再验证用户名和密码(需要花费0.05s~1s时间)。需要连接时建立一个连接,执行完成后断开,这种方式会耗费大量的资源和时间。**数据库连接资源没有得到很好的重复利用。**无法应对高负载情况
    • 对于每一次数据库连接,使用完之后都得关闭。如果因为程序异常而未能关闭,将会导致数据库系统内存泄漏,最终导致重启数据库
    • 不能控制被创建的连接对象数,系统资源会被毫无顾忌地分配出去,连接过多也可能导致内存泄露,服务器崩溃

8.2 数据库连接池技术

  • 基本思想:为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完再放回去。

  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

  • 最小数据库连接数:初始化创建。数据库连接池至少有的连接数

    最大数据库连接数:限定最大的连接数。超过的话,之后的请求会被加入到等待队列中

  • 工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWCPeT4y-1645449806331)(C:\Users\Tao\AppData\Roaming\Typora\typora-user-images\image-20220113135614756.png)]

  • 优点
    1. 资源重用
    2. 更快的系统反应速度
    3. 新的资源分配手段
    4. 统一的连接管理,避免数据库连接泄露

8.3 多种开源的数据库连接池

  • JDBC数据库连接使用javax.sql.DataSource来表示,DataSource只是一个接口,通常由服务器(Weblogic、WebSphere、Tomcat)实现,也有开源的:
    • DBCP:由Apache提供。速度相对C3P0快,但自身存在bug
    • C3P0:速度相对慢但稳定。hibernate官方推荐使用
    • Proxool
    • BoneCP
    • Druid:阿里提供的
  • DataSource用来取代DriverManager来获取Connection,获取速度较快,同时可以大幅度提高数据库访问速度

8.3.1 C3P0数据库连接池

  • 依赖
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
  • 方式一:显式创建
//方式一:
@Test
public void testGetConnection() throws Exception {
    //获取C3P0数据库连接池
    ComboPooledDataSource cpds = new ComboPooledDataSource();
    cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
    cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8" );
    cpds.setUser("root");
    cpds.setPassword("");

    //设置初始化时数据库连接池的连接数
    cpds.setInitialPoolSize(10);

    Connection conn = cpds.getConnection();
    System.out.println(conn);
}
  • 方式二:配置文件xml创建

    放在resources包下

<?xml version="1.0" encoding="utf-8" ?>
<c3p0-config>

    <named-config name="helloc3p0">
        <!--提供获取连接的4个基本信息-->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&amp;characterEncoding=utf-8</property>
        <property name="user">root</property>
        <property name="password"></property>

        <!--数据库连接池管理的基本信息-->
        <!--单次增加的连接数-->
        <property name="acquireIncrement">5</property>
        <!--初始化连接数-->
        <property name="initialPoolSize">10</property>
        <!--最少连接数-->
        <property name="minPoolSize">10</property>
        <!--最大连接数-->
        <property name="maxPoolSize">100</property>
        <!--维护的最多的Statement个数-->
        <property name="maxStatements">50</property>
        <!--每个连接可使用的最多的Statement个数-->
        <property name="maxStatementsPerConnection">2</property>

    </named-config>
</c3p0-config>

​ 测试

//方式二:使用配置文件
@Test
public void testGetConnection1() throws Exception {
    ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
    Connection conn = cpds.getConnection();
    System.out.println(conn);
}

8.3.2 DBCP数据库连接池

  • 依赖
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>commons-pool</groupId>
    <artifactId>commons-pool</artifactId>
    <version>1.5.5</version>
</dependency>
  • 方式一:显式创建
//方式一
@Test
public void testGetConnection() throws SQLException {
    //创建连接池
    BasicDataSource source = new BasicDataSource();
    //设置基本信息
    source.setDriverClassName("com.mysql.cj.jdbc.Driver");
    source.setUrl("jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8");
    source.setUsername("root");
    source.setPassword("");
    //设置连接池管理属性
    source.setInitialSize(10);
    source.setMaxActive(10);
    //...

    Connection conn = source.getConnection();
    System.out.println(conn);
}
  • 方式二:配置文件
//方式二:使用配置文件
@Test
public void testGetConnection1() throws Exception {
    Properties pros = new Properties();
    //方式一:
    //    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
    //方式二:
    FileInputStream is = new FileInputStream(new File("src/main/resources/dbcp.properties"));
    pros.load(is);
    DataSource source = BasicDataSourceFactory.createDataSource(pros);
    Connection conn = source.getConnection();
    System.out.println(conn);
}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
username=root
password=

8.3.3 Druid数据库连接池

  • Druid是阿里巴巴开源平台上的一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一

  • 依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.0</version>
</dependency>
  • 使用方式:使用配置文件
@Test
public void getConnection() throws Exception {
    Properties pros = new Properties();

    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
    pros.load(is);
    DataSource source = DruidDataSourceFactory.createDataSource(pros);
    Connection conn = source.getConnection();
    System.out.println(conn);
}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
username=root
password=

initialSize=10
maxActive=10

8.4 使用三种连接池技术实现的JDBCUtils

public class JDBCUtils {
    /**
    * @Description: 使用C3P0数据库连接池技术获取连接
    */
    static ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
    public static Connection getConnection1() throws Exception {
        Connection conn = cpds.getConnection();
        return conn;
    }

    /**
    * @Description: 使用DBCP数据库连接池技术获取连接
    */
    private static DataSource source;
    static {
        try {
            Properties pros = new Properties();
            FileInputStream is = new FileInputStream(new File("src/main/resources/dbcp.properties"));
            pros.load(is);
            source = BasicDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection2() throws Exception {
        Connection conn = source.getConnection();
        return conn;
    }
    /**
    * @Description: 使用Druid数据库连接池技术获取连接
    */
    private static DataSource source1;
    static {
        try {
            Properties pros = new Properties();

            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
            pros.load(is);
            source1 = DruidDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public  static Connection getConnection3() throws Exception {
        Connection conn = source1.getConnection();
        return conn;
    }
}

9 Apache-DBUtils实现CRUD操作

9.1 Apache-DBUtils简介

  • common-dbutils是Apache组织提供的一个开源JDBC工具类库,是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能
  • API:
    • org.apache.commons.dbutils.QueryRunner
    • org.apache.commons.dbutils.ResultSetHandler
    • 工具类:org.apache.commons.dbutils.Dbutils
  • Maven依赖
<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.3</version>
</dependency>

9.2 Apache-DBUtils使用

在使用QueryRunner的方法中,需要提供对应的ResultSetHandler,即结果的返回方式。ResultSetHandler是一个规范结果返回方式的接口。

其实现类包括:

实现类功能
ResultSetHandler(接口)以下所有实现类的实现的接口
BeanHandler用于封装表中的一条记录,返回一个对象
BeanListHandler用于封装表中的多条记录的集合List,返回对象的集合List
MapHandler用于封装表中的一条记录,返回一个集合Map;该Map中将字段及其值设定key=value
BeanListHandler用于封装表中的多条记录的集合,返回Map的集合List
ScalarHandler用于查询特殊值,返回一个对象
  • 增删改
@Test
public void testInsert(){
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        int insertCount = runner.update(conn, sql, "蔡徐坤", "caixukun@126.com", "1997-09-08");
        System.out.println("添加了"+insertCount+"条记录");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}
  • 查询
//查询
//BeanHandler:ResultSetHandler接口的实现类,用于封装表中的一条记录
@Test
public void testQuery1() {
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id = ?";
        BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
        Customer customer = runner.query(conn, sql, handler, 24);
        System.out.println(customer);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}
//BeanListHandler:ResultSetHandler接口的实现类,用于封装表中的多条记录的集合
@Test
public void testQuery2() {
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id < ?";
        BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
        List<Customer> list = runner.query(conn, sql, handler, 15);
        list.forEach(System.out::println);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}
//MapHandler:ResultSetHandler接口的实现类,用于封装表中的一条记录,返回Map
@Test
public void testQuery3() {
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id = ?";
        MapHandler handler = new MapHandler();
        Map<String, Object> map = runner.query(conn, sql, handler, 24);
        System.out.println(map);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}
//MapListHandler:ResultSetHandler接口的实现类,用于封装表中的多条记录,返回Map的集合List
@Test
public void testQuery4() {
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id < ?";
        MapListHandler handler = new MapListHandler();
        List<Map<String, Object>> list = runner.query(conn, sql, handler, 15);
        list.forEach(System.out::println);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}
//ScalarHandler:ResultSetHandler接口的实现类,用于查询特殊值
@Test
public void testQuery5(){
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select count(*) from customers";
        ScalarHandler handler = new ScalarHandler();
        long count = (long) runner.query(conn, sql, handler);
        System.out.println(count);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}

9.3 自定义ResultSetHandler实现类

正常Apache-DBUtils中提供的ResultSetHandler实现类已经足够,以下自定义只是演示方法。

//自定义ResultSetHandler实现类
@Test
public void testQuery7(){
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id = ?";
        ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
            @Override
            public Customer handle(ResultSet resultSet) throws SQLException {
                if(resultSet.next()){
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    String email = resultSet.getString("email");
                    Date birth = resultSet.getDate("birth");
                    Customer customer = new Customer(id, name, email, birth);
                    return customer;
                }
                return null;
            }
        };
        Customer customer = runner.query(conn, sql, handler, 20);
        System.out.println(customer);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}

9.4 Dbutils工具类实现资源关闭

/**
* @Description: 使用Dbutils工具类实现资源关闭
*/
public static void closeResource1(Connection conn, Statement ps, ResultSet rs){
    //closeQuietly()内封装了try-catch可以省去
    DbUtils.closeQuietly(conn);
    DbUtils.closeQuietly(ps);
    DbUtils.closeQuietly(rs);
}

ng) runner.query(conn, sql, handler);
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}


## 9.3 自定义ResultSetHandler实现类

正常Apache-DBUtils中提供的ResultSetHandler实现类已经足够,以下自定义只是演示方法。

```java
//自定义ResultSetHandler实现类
@Test
public void testQuery7(){
    Connection conn = null;
    try {
        QueryRunner runner = new QueryRunner();
        conn = JDBCUtils.getConnection3();
        String sql = "select id,name,email,birth from customers where id = ?";
        ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
            @Override
            public Customer handle(ResultSet resultSet) throws SQLException {
                if(resultSet.next()){
                    int id = resultSet.getInt("id");
                    String name = resultSet.getString("name");
                    String email = resultSet.getString("email");
                    Date birth = resultSet.getDate("birth");
                    Customer customer = new Customer(id, name, email, birth);
                    return customer;
                }
                return null;
            }
        };
        Customer customer = runner.query(conn, sql, handler, 20);
        System.out.println(customer);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.closeResource(conn,null);
    }
}

9.4 Dbutils工具类实现资源关闭

/**
* @Description: 使用Dbutils工具类实现资源关闭
*/
public static void closeResource1(Connection conn, Statement ps, ResultSet rs){
    //closeQuietly()内封装了try-catch可以省去
    DbUtils.closeQuietly(conn);
    DbUtils.closeQuietly(ps);
    DbUtils.closeQuietly(rs);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值