# JDBC 概述

[TOC]

  1. JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
  2. Java 程序员使用 JDBC, 可以连接任何提供了 JDBC 驱动程序的数据库系统,从而完成对数据库的各种操作

示意图:

image-20240105143447191

# JDBC 带来的好处:

示意图:

image-20240105143459996

说明: JDBC 是 Java 提供一套用于数据库操作的接口 API,Java 程序员需要面向这套接口编程即可,不同的数据库厂商,需要针对这套接口,提供不同实现

# JDBC API 概述:

示意图:

image-20240105143511737

JDBC API 是一系列的接口,它统一规范了应用程序与数据库的链接,执行 SQL 语句,并得到返回结果等各类操作,相关类和接口在 java.sql 与 javax.sql 包中

# JDBC 链接数据库方式 (路径以 Linux 为例)

方法功能
Driver注册驱动
Connection链接数据库
ResultSet通过 next 将指针向下移动,通过 previous 将指针向上移动 <br> 类似于迭代器,用于访问循环的数据,比如 DQL
PreparedStatement发送 SQL 到数据库,调用对应的 get 方法获取结果
Class.forNameJava 反射机制,记载类,返回 Class 类型对象
DriverManager替代 Driver 进行统一管理,可以调用 registerDriver 将注册驱动的对象 <br> 传入构造器中,使期为自身 < br > 通过调用 getConnection 获取与 MySQL 连接
Properties配置文件类,用于读取配置文件写出配置文件

Java 连接数据库

image-20240105143526160

# 1. 传统方式

@Test
    public void test () throws SQLException {
        // 方式一
        // 注册驱动
        Driver driver = new Driver();
        // 编写连接数据库的 url
        String url = "jdbc:mysql://localhost:3306/Demo";
        // 创建 Properties 配置文件类
        Properties properties = new Properties();
        // 设置配置文件信息
        properties.setProperty("user","root");
        properties.setProperty("password","dkx");
        // 创建 Connection 通过 Driver 对象调用 connect 添加 url 跟数据库的 user 和 password 连接数据库
        Connection connection = driver.connect(url,properties);
        // 编写一条 sql 语句
        String sql = "update `Demo` set name = '刘桑' where id = 28";
        // 将 sql 语句发送给 MySQL 数据库并返回其对象
        Statement statement = connection.createStatement();
        // 通过返回对象调用 executeUpdate 返回一个 int 类型的值
        int i = statement.executeUpdate(sql);
        // 判断这个值如果大于 1 则说明执行成功,否则失败
        System.out.println(i > 0 ? "连接成功" : "连接失败");
        // 关闭源,释放资源
        connection.close();
        statement.close();
    }

# 2. 使用反射机制

利用反射机制来实例化 Driver 对象从而注册驱动,然后向下转型即可调用 Driver 方法

image-20240105143538463

@Test
    public void testone () throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        Class cls = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver)cls.newInstance();
        String url = "jdbc:mysql://localhost:3306/Demo";
        Properties properties = new Properties();
        properties.setProperty("user","root");
        properties.setProperty("password","dkx");
        Connection connection = driver.connect(url,properties);
        System.out.println("Driver:"+connection);
    }

# 区别:

  • Driver driver = new Driver ( ); 直接使用 import.java.sql.Connection; 属于静态加载灵活性差依赖强
  • Class cls = Class.forName ("com.mysql.jdbc.Driver"); 属于动态加载,灵活性强

# 3. 使用 DriverManager 替代 Driver 进行统一管理

@Test
    public void testtwo () throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //Java 反射机制实例化 Driver 类
        Class cls = Class.forName("com.mysql.jdbc.Driver");
        // 向下转型
        Driver driver = (Driver)cls.newInstance();
        String url = "jdbc:mysql://localhost:3306/Demo";
        String user = "root";
        String password = "dkx";
        // 使用 DriverManager 注册给定的驱动程序,调用 resterDriver 使其为自身
        DriverManager.registerDriver(driver);
        // 通过 Connection 提供 url,user,password 连接数据库
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println("Driver:"+connection);
    }

# 4. 使用反射机制加载 Driver 类

@Test
    public void testsan () throws ClassNotFoundException, SQLException {
        // 通过反射机制加载 Driver 类
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/Demo";
        String user = "root";
        String password = "dkx";
        // 通过 DriverManager 调用 getConnection 方法传入数据库的 url,user,password 连接到数据库
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println(connection);
    }

# 5. 在第四个方式上进行优化

@Test
    public void testsi () throws ClassNotFoundException, IOException, SQLException {
        // 实例化配置文件对象
        Properties properties = new Properties();
        // 向配置文件中添加信息
        properties.setProperty("driver","com.mysql.jdbc.Driver");
        properties.setProperty("url","jdbc:mysql://localhost:3306/Demo");
        properties.setProperty("user","root");
        properties.setProperty("password","dkx");
        // 通过调用 store 方法新建输出流将配置文件写出到指定位置中,第二个参数为注释
        properties.store(new FileOutputStream("src//re.Properties"),"LinuxProperties");
        // 获取配置文件中的信息
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String password = properties.getProperty("password");
        String user = properties.getProperty("user");
        // 通过反射机制加载 Driver 类,虽然自动加载但是还是建议写上
        Class.forName(driver);
        // 实例化 Connection 类通过 DriverManager 调用 getConnection 方法传入连接数据库的数据
        Connection connection = DriverManager.getConnection(url,user,password);
        // 编写一条 SQL 语句
        String sql = "select * from Demo";
        // 向数据库发送 SQL 语句,并返回其对象
        Statement statement = connection.createStatement();
        // 通过 Statement 对象调用 executeQuery 执行 SQL DQL 语句并返回给 ResultSet 对象
        ResultSet resultset = statement.executeQuery(sql);
        // 因为不知道循环多少次,使用 while 循环来处理 resultset.next () 每次执行指向下一个
        // 当没有可查询列后返回 false 即结束循环
        while(resultset.next()){
            //id 在第一列中为 int 类型
            int id = resultset.getInt(1);
            String name = resultset.getString(2);
            String sex = resultset.getString(3);
            Date date = resultset.getDate(4);
            String dian = resultset.getString(5);
            System.out.println(id+"\t"+name+"\t"+sex+"\t"+date+"\t"+dian);
        }
        // 关闭源,释放资源
        connection.close();
        statement.close();
    }
  • 提示:

    1. mysql 驱动 5.1.6 可以无需 Class.forName ("com.mysql.jdbc.Driver");

    2. 从 jdk1.5 以后使用了 jdbc4, 不再需要显示调用 Class.forName ( ); 注册驱动而是自动调用驱动 jar 包下 META-INF\serverces\java.sql.Driver 文件中的类名称去注册

      image-20240105143556571

    3. 建议加上 Class.forName ("com.mysql.jdbc.Driver"); 更加明确

    Debug 代码查看 ResultSet 对象结构:

    image-20240105143624437

# Statement

  1. Statement 对象用于执行静态 SQL 语句并返回其生成的结果对象
  2. 在连接建立后,需要对数据库进行访问,执行命令或是 SQL 语句,可以通过
  • Statement (存在 SQL 注入)
  • PreparedStatement (预处理)
  • CallableStatement (存储过程)
  1. Statement 对象执行 SQL 语句,存在 SQL 注入风险
  2. SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库
  3. 要防范 SQL 注入,只要用 PreparedStatement**(从 Statement 扩展而来)** 取代 Statement 就可以了

# 演示 SQL 注入:

表结构和数据:

-- auto-generated definition
create table admin
(
    name varchar(64) default '' not null,
    pwd  varchar(64) default '' not null
)
    collate = utf8_bin;
  • 操作:将用户名和密码输入为 name = 1' or password = or '1' = '1 如果不成功建议按顺序打 '->1->'
# 演示 SQL 注入
# 创建一张表
create table admin (
    `name` varchar(64) not null default '',
    `pwd` varchar(64) not null default ''
)character set utf8 collate utf8_bin;
# 查找某个管理员是否存在,输入用户名为 '1' or' 输入密码为 'or = '1' = '1'
select * from admin
where name = '1' or' and pwd = 'or '1' = '1';
  • Java 程序中演示
Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名与密码");
        System.out.print("请输入用户名:");
        String zhang = sc.nextLine();
        System.out.print("请输入密码:");
        String mima = sc.nextLine();
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.Properties"));
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        Class.forName(driver);
        String sql = "select * from admin where name = '"+zhang+"'and pwd = '"+mima+"'";
        Connection connection = DriverManager.getConnection(url,user,password);
        Statement statement = connection.createStatement();
        ResultSet resultset = statement.executeQuery(sql);
        if(resultset.next()){
            System.out.println("登入成功");
        }else{
            System.out.println("登入失败");
        }
        connection.close();
        statement.close();

执行输入密码演示:

输入正确密码

image-20240105143641132

输入错误密码或者是不存在的用户名

image-20240105143654524

使用万能密码演示:

image-20240105143704277

# PreparedStatement

  1. PreparedStatement 执行的 SQL 语句中的参数用问号 (?) 来表示,调用 PreparedStatement 对象的 setXxx ( ); 方法来设置这些参数,setXxx ( ); 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引 (从 1 开始), 第二个是设置的 SQL 语句中的参数的值
  2. 调用 executeQuery ( ); 执行 DQL 语句,返回 ResultSet 对象
  3. 调用 executeUpdate ( ); 执行 DML 语句,更新,包括增,删。改
String sql = "select count(*) from admin where name = ? and password = ?";

# PreparedStatement 好处

  1. 不再使用 + 拼接 SQL 语句,减少语句错误
  2. 有效的解决了 SQL 注入问题
  3. 大大减少了编译次数,效率较高

代码:登入演示:(这个程序在使用万能密码的时候就不管用了)

演示是否可以使用 admin name 1' or admin password or '1' = '1

package com.dkx.xer;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
@SuppressWarnings({"all"})
public class My_Connection {
    public static void main (String[]args) throws IOException, ClassNotFoundException, SQLException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入内容");
        System.out.print("请输入用户名:");
        String admin_name = sc.nextLine();
        System.out.print("请输入密码:");
        String admin_neir = sc.nextLine();
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.Properties"));
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        Class.forName(driver);
        //Connection 连接数据库
        Connection connection = DriverManager.getConnection(url,user,password);
        // 得到 PreparedStatement
        //1. 编写 SQL 语句 ? 号相当于是一个占位符
        String sql = "select * from admin where name = ? and pwd = ?";
        //2.PreparedStatement 对象实现了 PreparedStatement 接口的实现类对象
        PreparedStatement preparedstatement = connection.prepareStatement(sql);
        //3. 通过 PreparedStatement 给?赋值,进行了预处理
        preparedstatement.setString(1,admin_name);
        preparedstatement.setString(2,admin_neir);
        //4. 执行 select   DQL 语句使用 executeQuery ();
        // 如果执行的是 DML (update,insert,delete) 则使用 executeUpdate ();
        // 这里的参数中不能给 sql, 这里赋值 sql 对象的话,相当于将带有?号的语句给了这个对象就会发生报错
        ResultSet resultset = preparedstatement.executeQuery();
        if(resultset.next()){
            System.out.println("登入成功");
        }else{
            System.out.println("抱歉,登入失败!");
        }
        resultset.close();
        preparedstatement.close();
        connection.close();
    }
}

# 最常用 jdbc API

  • DriverManager 驱动管理类 ---> getConnection (url,user,password) 获取到链接

  • Connection 接口 ---> createStatement 创建 Statement 对象

​ ---> preparedStatement (sql) 生成预处理对象


  • Statement 接口 ---> executeUpdate (sql) 执行 DML 语句,返回影响的行数

​ ---> executeQuery (sql) 执行查询,返回 ResultSet 对象

​ ---> execute (sql) 执行任意的 sql, 返回布尔值一般用于创建表


  • PreparedStatement 接口 ---> executeUpdate ([这里面不能写 sql]); 执行 DML (增删改查)

​ ---> executeQuery ([不能写 sql 对象]); 执行查询,返回 ResultSet DQL

​ ---> execute ([不能写 sql 对象]); 执行任何 sql, 返回布尔值一般用于创建表

​ ---> setXxx (占位符索引,占位符的值); 解决 SQL 注入 [Xxx 为对应数据类型]

​ ---> setObject (占位符索引,占位符的值);


  • ResultSet (结果集) ---> next ( ); 向下移动一行,同时如果没有下一行,返回 false

​ ---> previous ( ); 向上移动一行

​ ---> getXxx (列的索引 | 列名); 返回对应列的值,接口类型是 Xxx

​ ---> getObject (列的索引 | 列名); 返回对应列的值,接收类型为 Object 类型

# 封装 JDBCUtils

示意图:

image-20240105143722652

  • 构建 JDBCUtils 工具类

思路:

  1. 编写属性 (4 个) 分别为 (driver,url,user,password) 并且声明字段为私有,静态类型成员变量
  2. 编写一个 static 静态代码块,方法体中编写:Properties 对象,用于读取相关的属性,(driver,url,user,password), 将抛出的异常编写为运行时异常,这样,即可以选择处理也可以选择默认处理,比较方便
  3. 编写连接数据库的 Connection 方法并且这个方法为返回值类型返回 Connection 类,命名为 getConnection, 方法体中编写 return 返回 DriverManager.getConnection (url,user,password); 抛出异常也为运行时异常
  4. 关闭相关资源,编写方法四个参数分为在调用这个方法时关闭所需要关闭的流,如果有不需要关闭的则置为 null 即可
package com.dkx.jdbcutils;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
public class Jdbcutils {
    // 定义相关的属性(4 个)因为只需要一份,因此,为 static
    private static String driver;
    private static String url;
    private static String user;
    private static String password;
    static {
        try{
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.Properties"));
        // 读取相关的属性
             driver = properties.getProperty("driver");
             url = properties.getProperty("url");
             user = properties.getProperty("user");
             password = properties.getProperty("password");
             // 注册驱动
             Class.forName("driver");
        }catch(Exception e){
            // 在实际开发中,可以将编译异常转成运行时异常
            // 调用者可以选择捕获该异常,也可以选择默认处理,比较方便
            throw new RuntimeException(e);
        }
    }
    // 连接数据库,返回 Connection
    public static Connection getConnection () {
        try{
            return DriverManager.getConnection(url,user,password);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    // 关闭相关资源
    /*
    1.ResultSet 结果集
    2.Statement 或者 PreparedStatement
    3.Connection
    4. 如果需要关闭资源,就传入对象,否则传入 null
     */
    public static void close (ResultSet set, PreparedStatement preparestatement, Connection connection) {
        // 判断是否为 null
        try{
            if(set != null){
                set.close();
            }
            if(preparestatement != null){
                preparestatement.close();
            }
            if(connection != null){
                connection.close();
            }
        }catch(Exception e){throw new RuntimeException(e);}
    }
}
  • 编写执行 SQL 类

思路:

编写一个方法,方法体中编写

  1. 得到连接创建 Connection 对象
  2. 编写一条 SQL 执行语句
  3. 编写 PreparedStatement 对象,执行 SQL 语句
  4. 得到 PreparedStatement 对象返回的结果
  5. 最后通过 JDBCUtils 类调用 close 方法来传入需要关闭的流没有则置为 null
package com.dkx.jdbcutils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class Jdbcutils_use {
    public static void main(String[]args){
        // 实例化对象
        var ar = new Jdbcutils_use();
        // 对象调用方法执行操作数据库
        ar.test_connector();
    }
    public void test_connector () {
        //1. 得到连接
        Connection connection = null;
        //2. 编写一条 SQL 语句
        String sql = "insert into admin values (?,?)";
        //3. 创建 PreparedStatement 对象,扩大 PrearedStatement 的作用域
        PreparedStatement preparedstatement = null;
        try{
            connection = Jdbcutils.getConnection();
            preparedstatement = connection.prepareStatement(sql);
            // 给占位符进行赋值
            preparedstatement.setString(1,"张三");
            preparedstatement.setString(2,"123");
            int i = preparedstatement.executeUpdate();
            if(i > 0){
                System.out.println("添加成功~");
            }else{
                System.out.println("添加失败!");
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            // 关闭资源
            Jdbcutils.close(null,preparedstatement,connection);
        }
    }
}

总结:

  • 编写两个类一个为工具类,一个为执行 SQL 语句的类

  • 工具类中定义静态成员变量,并私有化防止外部程序破坏,编写静态代码块用于在类被加载时加载一次,方法体中编写 Properties 读取配置文件中的相关信息,并返回给私有静态成员变量

  • 工具类中编写,读取配置文件类,并在静态代码块中注册驱动,编写连接数据库的方法,通过类名调用返回连接数据库的信息,编写关闭相关资源的方法,都为静态方法方便使用类名直接调用而不实例化类

  • 执行 SQL 语句类:编写一个方法,方法体中创建 Connection 对象由 JDBCUtils 工具类调用 getConnection 获取链接数据库的信息,编写 PreparedStatemen 执行 SQL,PreparedStatement 对象调用 setXxx 对占位符进行赋值,最后 fianlly 代码块中 JDBCUtils 工具类调用 close 方法传入需要关闭的相关资源

# 事务

基本介绍:

  1. JDBC 程序中当一个 Connection 对象创建时,默认情况下是自动提交事务的:每次执行一个 SQL 语句时,如果执行成功,就会像数据库自动提交,而不能回滚
  2. JDBC 程序中为了让多个 SQL 语句作为一个整体执行,需要使用事务
  3. 调用 Connection 的 setAutoCommit (false) 可以取消自动提交事务
  4. 在所有的 SQL 语句都成功执行后,调用 commit ( ); 方法提交事务
  5. 在其中某个操作失败或出现异常时,调用 rollback ( ); 方法回滚事务
方法功能声明
setAutoCommit(false)开启或禁用事务的自动提交事务
commit( );使自上次提交 / 回滚以来所做的所有更改成为永久更改 <br> 并释放此 Connection 对象当前持有的所有数据库锁 < br > 仅在禁用自动提交模式时才应使用此方法
rollback( );撤销当前事务中所做的所有更改,并释放此 Connection<br> 对象当前持有的所有数据库锁,仅在禁用自动提交模式时才 < br > 应使用此方法 < br > 异常 SQLException : 如果发生数据库访问错误,则在参与分布式 < br > 事务时调用此方法,此方法在已关闭的链接上调用,或者此 < br>Connection 对象处于自动提交模式
rollback(Savepoint savepoint);取消在设置给定的 Savepoint 对象后所做的所有更改 <br> 仅在禁用自动提交事务时才应使用此方法 < br > 异常 SQLException : 如果发生数据库访问错误,则在参与分布式事务时调用此方法,在关闭的链接上调用此方法,Savepoint 对象不再 < br > 有效,或者此 Connection 对象当前处于自动提交模式 < br>SQLFeatureNotSupporedException - 如果 JDBC 驱动程序不 < br > 支持此方法
package com.transaction;
import com.music.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Scanner;
public class Salary_test {
    public static void main(String[]args) {
        var ar = new Salary_test();
        ar.test();
    }
    public void test () {
        Scanner sc = new Scanner(System.in);
        System.out.print("转账金额:");
        Double salary = sc.nextDouble();
        Scanner sc1 = new Scanner(System.in);
        System.out.print("转账者:");
        String name = sc1.nextLine();
        System.out.print("收账者:");
        String name1 = sc1.nextLine();
        Connection connection = null;
        PreparedStatement preparedstatement = null;
        String sql = "update `salary` set salary = salary - ? where name = ?";
        String sql1 = "update `salary` set salary = salary + ? where name = ?";
        try{
            connection = JDBCUtils.getConnection();// 在默认情况下,Connection 是默认自动提交的
            // 将 connection 设置为不自动提交事务
            connection.setAutoCommit(false);// 开启了事务
            preparedstatement = connection.prepareStatement(sql);
            preparedstatement.setDouble(1,salary);
            preparedstatement.setString(2,name);
            preparedstatement.executeUpdate();
            // 程序报错...
            System.out.println(1/0);
            preparedstatement = connection.prepareStatement(sql1);
            preparedstatement.setDouble(1,salary);
            preparedstatement.setString(2,name1);
            int i = preparedstatement.executeUpdate();
            connection.commit();
            System.out.println(i > 0? "转账成功" : "转账失败");
        }catch(Exception e){
            // 这里我们可以进行回滚,即撤销执行的 SQL 操作
            // 默认回滚到事务开始的状态
            System.out.println("执行操作有错误,事务已经回滚");
            try{
                connection.rollback();
            }catch(Exception er){
                er.printStackTrace();
            }
            e.printStackTrace();
        }finally{
            JDBCUtils.close(connection,preparedstatement,null);
        }
    }
}

# 批处理:

基本介绍:

  1. 当需要成批插入或者更新新纪录时,可以采用 Java 的批量更新机制,这一机制允许多条 SQL 语句一次性提交给数据库批量处理,通常情况下单独提交处理更有效

  2. JDBC 的批量处理语句包括下面方法:

    <font color = red> addBatch( ); 添加需要批量处理的语句 </font>

    <font color = red> executeBatch( ); 执行批量处理语句 </font>

    <font color = red> clearBatch( ); 清空批处理包的语句 </font>

  3. JDBC 连接 MySQL 时,如果要使用批处理功能,请再 url 中加参数?rewriteBatchedStatements=true

    批处理必须指定这个参数否则将会无效

  4. 批处理往往和 PreparedStatement 一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高

代码演示:

时间对比:

传统方式:执行用时:8423

批量处理方式:执行用时:954

public void test () {
        Connection connection = null;
        PreparedStatement preparedstatement = null;
        String sql = "insert into `salary` values (?,?)";
        long start = System.currentTimeMillis();
        try{
            connection = JDBCUtils.getConnection();
            preparedstatement = connection.prepareStatement(sql);
            System.out.println("添加中...");
            for(int i = 0;i < 5000;i ++){
                preparedstatement.setString(1,"张三");
                preparedstatement.setDouble(2,1+i);
                preparedstatement.executeUpdate();
            }
            System.out.println("添加完毕");
            long pause = System.currentTimeMillis();
            System.out.println("执行用时:"+(pause - start));// 执行用时:8423
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            JDBCUtils.close(connection,preparedstatement,null);
        }
    }
    public void test1 () {
        Connection connection = null;
        PreparedStatement preparedstatement = null;
        String sql = "insert into `salary` values (?,?)";
        long start = System.currentTimeMillis();
        try{
            connection = JDBCUtils.getConnection();
            preparedstatement = connection.prepareStatement(sql);
            System.out.println("添加中...");
            for(int i = 0;i < 5000;i ++){
                preparedstatement.setString(1,"张三");
                preparedstatement.setDouble(2,1+i);
                preparedstatement.addBatch();
                if(i == 4999){
                    preparedstatement.executeBatch();
                    preparedstatement.clearBatch();
                }
            }
            System.out.println("添加完毕");
            long pause = System.currentTimeMillis();
            System.out.println("执行用时:"+(pause - start));// 执行用时:954
        }catch(Exception e){
            e.printStackTrace();
        }
    }

# 数据库连接池

传统获取 Connection 问题分析

  1. 传统的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证 <font color = blue>IP 地址 </font>,<font color = blue> 用户名 </font> 和 < font color = blue> 密码 </font>,(0.05s~1sTime), 需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃
  2. 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库 <font color =blue> 内存泄露 </font>, 最终将导致重启数据库
  3. 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致 <font color =blue> 内存泄露 </font>,MySQL 崩溃
  4. 解决传统开发中的数据库连接问题,可以采用 <font color = red> 数据库连接池技术 (connection pool)</font>

image-20221027183545806

# 数据库连接池基本介绍

  1. 预先在缓冲池中存放入一定数量的连接,当需要建立数据库连接时,只需从 "缓冲池" 中取出一个,使用完毕之后再放回去

  2. 数据库连接池负责分配,管理和释放数据库链接,它允许应用程序 <font color = red> 重复使用 </font> 一个现有的数据库连接,而不是重新建立一个

  3. 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中

    image-20240105143808707

# 数据库连接池种类

  1. JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由第三方提供实现
  2. <font color = green>C3P0</font > 数据库连接池,速度相对较慢,稳定性不错 (hibernate,spring)
  3. DBCP 数据库连接池,速度相对 C3P0 较快,但不稳定
  4. Proxool 数据库连接池,有监控连接池状态的功能,稳定性较 iCP 差一点
  5. BoneCP 数据库连接池,速度快
  6. <font color = grenn>Druid (德鲁伊)</font> 是阿里提供的数据库连接池,集 DBCP,C3P0,Proxool 优点于一身的数据库连接池

# C3P0 使用:

  1. 将 c3p0-0.9.5.5.bin.zip 包解压,使用指令
unzip c3p0-0.9.5.5.bin.zip
  1. cd 进入解压后的 zip 包找到 lib 包进入找到 c3p0-0.9.5.5.jar 的包和 mchange-commons-java-0.2.19.jar 包

    image-20240105143827227

  2. copy 将这两个包都 paste 到 IDEA 的 libs 文件夹中

    image-20240105143840760

  3. 再将 libs 文件夹导入到项目中

右键文件夹点击 Add as Library....

image-20240105143907300

确定好要导入的项目后点击 OK

image-20240105143916710

c3p0-0.9.5.5-config.xml 文件配置信息

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <named-config name="dkx_123">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property><!-- 这是你的数据库驱动 -->
        <!-- 这是你的数据库,当然如果是本地连接也可把 localhost:3303 给删掉 -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/Demo?characterEncoding=utf-8&amp;serverTimezone=UTC</property>
        <!-- 这是你的数据库账号 密码 -->
        <property name="user">root</property>
        <property name="password">root</property>
        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property><!-- 初始化连接数 -->
        <property name="maxPoolSize">10</property><!-- 最大连接数 -->
        <property name="checkoutTimeout">3000</property><!-- 超时时间      -->
    </named-config>
    <!-- 这是自定义的配置,如果想用这个配置        创建的时候这样写就好了    javax.sql.DataSource com  = new ComboPooledDataSource ("outerc3p0");-->
    <!-- 这属性同上 -->
    <named-config name="otherc3p0">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mysql</property>
        <property name="user">root</property>
        <property name="password"></property>
        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">8</property>
        <property name="checkoutTimeout">1000</property>
    </named-config>
</c3p0-config>
  • 代码:
package com.testconnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
@SuppressWarnings({"all"})
public class C3P0_test {
    public static void main(String[]args) throws PropertyVetoException, SQLException, IOException {
        // 方式 1: 相关参数,在程序中指定 user,url,password 等
        ComboPooledDataSource combopooleddatasource = new ComboPooledDataSource();
        // 通过配置文件 re.Properties 获取相关连接的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//re.Properties"));
        // 读取相关的属性值
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        // 给数据源 ComboPooledDataSource 设置相关参数
        // 注意:连接管理是由 ComboPooledDataSource 来管理
        combopooleddatasource.setDriverClass(driver);
        combopooleddatasource.setJdbcUrl(url);
        combopooleddatasource.setUser(user);
        combopooleddatasource.setPassword(password);
        // 设置初始化连接数
        combopooleddatasource.setInitialPoolSize(10);
        // 设置最大连接数
        combopooleddatasource.setMaxPoolSize(50);
        // 测试连接池的效率   测试对 MySQL 操作 5000 次
        System.out.println("连接成功...");
        long start = System.currentTimeMillis();
        for(int i = 0;i < 5000;i++){
            // 这个方法就是从 DataSource 接口实现
            Connection connection = combopooleddatasource.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        //C3P0 5000 次操作 执行耗时:1002
        System.out.println("C3P0 5000 次操作 执行耗时:"+(end - start));
    }
}
  • 使用配置文件连接
@Test
    public void test () throws SQLException {
        // 创建 ComboPooledDataSource 对象 构造器中赋值连接的 named-config : Name
        ComboPooledDataSource combopooleddatasource = new ComboPooledDataSource("dkx_123");
        System.out.println("连接成功");
        long start = System.currentTimeMillis();
        for(int i = 0;i < 5000;i++){
            // 通过 ComboPooledDataSource 对象调用 getConnection 进行连接数据库
            Connection connection = combopooleddatasource.getConnection();
            // 关闭数据库连接,释放资源
            connection.close();
        }
        long end = System.currentTimeMillis();
        // 执行耗时:966
        System.out.println("执行耗时:"+(end - start));
    }

# Mysql8 使用 c3p0 连接配置

目录结构 (参考)

image-20240311151041197

编写 c3p0-config.xml 文件

将该文件放到 src 目录下 (普通工程)

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!-- 连接参数 -->
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/?characterEncoding=utf-8&amp;serverTimezone=UTC&amp;useUnicode=true</property>
        <property name="user">root</property>
        <property name="password"></property>
        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">3000</property>
    </default-config>
</c3p0-config>

JdbcUtils 类

public class JdbcUtils {
    //c3p0 数据库连接池对象属性
    private static final ComboPooledDataSource ds = new ComboPooledDataSource();
    // 获取连接
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    // 释放资源
    public static void release(AutoCloseable... ios) {
        for (AutoCloseable io : ios) {
            if (io != null) {
                try {
                    io.close();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    // 提交事务并释放资源
    public static void commitAndClose(Connection conn) {
        if (conn != null) {
            try {
                // 提交事务
                conn.commit();
                // 释放资源
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    // 回滚事务并释放资源
    public static void rollbackAndClose(Connection conn) {
        if (conn != null) {
            try {
                // 回滚事务
                conn.rollback();
                // 释放资源
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

# 使用案例

dao

public class AccountDao {
    // 转出
    public void out(String outUser, int money) throws SQLException {
        String sql = "update account set money = money - ? where name = ?";
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement pstm = connection.prepareStatement(sql);
        pstm.setInt(1, money);
        pstm.setString(2, outUser);
        pstm.executeUpdate();
        JdbcUtils.release(pstm, connection);
    }
    // 转入
    public void in(String inUser, int money) throws SQLException {
        String sql = "update account set money = money + ? where name = ?";
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement pstm = connection.prepareStatement(sql);
        pstm.setInt(1, money);
        pstm.setString(2, inUser);
        pstm.executeUpdate();
        JdbcUtils.release(pstm, connection);
    }
}

service

public class AccountService {
    // 转账
    public boolean transfer(String outUser, String inUser, int money) {
        AccountDao ad = new AccountDao();
        try {
            // 转出
            ad.out(outUser, money);
            // 转入
            ad.in(inUser, money);
        } catch (SQLException e) {
            return false;
        }
        return true;
    }
}

main

public static void main(String[] args) {
   // 模拟数据:Jack 给 Rose 转账 100 元
   String outUser = "Jack";
   String inUser = "Rose";
   int money = 100;
   AccountService service = new AccountService();
   boolean result = service.transfer(outUser, inUser, money);
   if (result == false) {
      System.out.println("转账失败");
   } else {
      System.out.println("转账成功");
   }
}

执行结果:

转账成功

# Druid 使用

druid.Properties 配置文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/Demo?rewriteBathedStatements=true
username=root
password=dkx
initialSize=10
minIdle=5
maxActive=20
maxWait=5000

如果使用上述的配置信息出现了无法创建连接则使用下面的配置信息

原因:MySQL 版本问题

driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14?characterEncoding=utf-8&serverTimezone=UTC
username=root
password=dkx
initialSize=10
minIdle=5
maxActive=20
maxWait=5000
  • Druid 连接与 C3P0 连接测试
  • Druid: 执行耗时: 3629
package com.testconnection;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
@SuppressWarnings({"all"})
public class Druid {
    public static void main(String[]args) throws Exception {
        //1. 加入 Druid.jar 包
        //2. 加入配置文件 druid.Propertis, 将该文件拷贝项目的 src 目录
        //3. 创建 Properties 对象,读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src//druid.Properties"));
        //4. 创建一个指定参数的数据库连接池
        DataSource druiddatasourcefactory = DruidDataSourceFactory.createDataSource(properties);
        long start = System.currentTimeMillis();
        for(int i = 0;i < 5000000;i++){
            Connection connection = druiddatasourcefactory.getConnection();
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("Druid执行耗时:"+(end - start));//Druid 执行耗时:3629
        System.out.println("执行结束!");
    }
}
  • C3P0 执行耗时: 17491
package com.testconnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
@SuppressWarnings({"all"})
public class C3P0_test {
    public static void main(String[] args) throws SQLException {
        ComboPooledDataSource combopooleddatasource = new ComboPooledDataSource("dkx_123");
        System.out.println("连接成功");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000000; i++) {
            // 通过 ComboPooledDataSource 对象调用 getConnection 进行连接数据库
            Connection connection = combopooleddatasource.getConnection();
            // 关闭数据库连接,释放资源
            connection.close();
        }
        long end = System.currentTimeMillis();
        // 执行耗时:966
        System.out.println("执行耗时:" + (end - start));// 执行耗时:17491
    }
}

# ResultSet 复用,引出问题

  1. 关闭 Connection 后,ResultSet 结果集无法使用
  2. ResultSet 不利于数据的管理

示意图:

image-20240105143932367

# 使用土办法完成封装来解决问题:

  • 代码演示:

JDBCUtils_Druid 工具类

package com.druidtest;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class JDBCUtils_Druid {
    //        定义获取数据库连接对象
    private static DataSource druid;
    static {
        try{
            // 创建 Properties 类读取配置文件信息
            Properties properties = new Properties();
            // 读取指定位置的配置文件
            InputStream in = JDBCUtils_Druid.class.getClassLoader().getResourceAsStream("druid.Properties");
            properties.load(in);
            // 将读取到配置文件的信息赋值到 DataSource 对象中
            druid = DruidDataSourceFactory.createDataSource(properties);
        }catch(Exception e){
            throw new RuntimeException();
        }
    }
    // 定义连接方法返回 Connection 连接
    public static Connection getConnection () {
        try{
            return druid.getConnection();
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    // 关闭连接,如果没有需要关闭则赋值为 null
    public static void close(Connection connection, ResultSet resultset, PreparedStatement set){
        try{
            if(connection != null){
                connection.close();
            }
            if(resultset != null){
                resultset.close();
            }
            if(set != null){
                set.close();
            }
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}

Actor 封装 ResultSet 结果集,解决 ResultSet 复用问题,因为 List 和 Connection 没有任何关联,所以该集合可以复用,即使关闭 Connection 连接,也不会影响到 List

package com.druidtest;
import java.sql.Date;
//Actor 对象对应 Demo 表
public class Actor {//Javabean,DoJO,Domain 对象
    // 定义表中对应的字段信息
    private Integer id;
    private String name;
    private String sex;
    private Date borndata;
    private String phone;
    public Actor(){}// 一定要给一个无参构造器 [底层反射需要]
    // 创建一个有参构造用于获取字段的信息
    public Actor(Integer id, String name, String sex, Date borndata, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndata = borndata;
        this.phone = phone;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBorndata() {
        return borndata;
    }
    public void setBorndata(Date borndata) {
        this.borndata = borndata;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    // 重写 toString 返回字符串类型的对象信息
    @Override
    public String toString() {
        return "牛马:{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", borndata=" + borndata +
                ", phone='" + phone + '\'' +
                '}';
    }
}

测试类

package com.druidtest;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.LinkedList;
import java.util.List;
public class Druidtest_two {
    public static void main(String[]args){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultset = null;
        // 创建一个 List 集合对象用于存储 ResultSet 获取到的字段的信息,解决 ResultSet 复用问题
        List<Actor> list = new LinkedList<>();
        // 编写一个 SQL 语句
        String sql = "select * from Demo";
        try{//try 将可能存在的异常抛出
            // 通过 JDBCUtils_Druid 类调用 getConnecion 方法获取连接返回 Connection 对象
            connection = JDBCUtils_Druid.getConnection();
            // 创建 PreparedStatement 对象将 SQL 语句发送给数据库
            statement = connection.prepareStatement(sql);
            // 通过 PreparedStatement 对象调用 executeQuery 来获取返回的结果
            resultset = statement.executeQuery();
            while(resultset.next()){//resultset 调用 next 每次执行指针向下移动获取下一个信息,如果没有信息则返回 false
                // 通过调用 getXxx 获取对应的数据类型
                Integer id = resultset.getInt("id");
                String name = resultset.getString("name");
                String sex = resultset.getString("sex");
                Date date = resultset.getDate("borndata");
                String phone = resultset.getString("phone");
                // 把得到的 ResultSet 的记录,封装到 Actor 对象,放入到 List 集合中
                list.add(new Actor(id,name,sex,date,phone));
            }
            for(Actor i:list){
                System.out.print(i);
                // 通过封装可以完成随意取数据的效果
//                System.out.print(i.getId());
//                System.out.print(i.getName());
                System.out.println();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            // 调用 JDBCUtils_Druid 的 close 方法传入需要关闭的连接对象,关闭流释放资源
            JDBCUtils_Druid.close(connection,resultset,statement);
        }
    }
}

通过封装可以完成随意取数据的效果,在取数据的时候调用 Actor 类中的 get 方法来获取字段的信息

下面介绍 Apche-DBUtils 工具类解决问题

# Apche-DBUtils

<font color = blue> 基本介绍 </font>

  1. commons-dbutils 是 Apche 组织提供的一个开源 JDBC 工具类库,它是对 JDBC 的封装,使用 dbutils 能极大简化 jdbc 编码的工作量

<font size = 4,font color >dbutils 类 </font>

  1. QueryRunner 类:该类封装了 SQL 的执行,是线程安全的,可以实现增,删,改,查,批处理
  2. 使用 QueryRunner 类实现查询
  3. ResultSetHandler 接口:该接口用于处理 java.sql.ResultSet , 将数据按要求转换为另一种形式

<font size = 4,font color = blue> ArrayHandler: </font> 把结果集的第一行数据转换成对象数组

<font size = 4,font color = blue> ArrayListHandler: </font> 把结果集中的每一行数据都转成一个数组,再存放到 List 中

<font size = 4,font color = blue> BeanHandler: </font> 将结果集中的第一行数据封装到一个对应的 JavaBean 实例中

<font size = 4,font color = blue> BeanListHandler: </font> 将结果集中的每一行数据都封装到一个对应的 JavaBean 实例中,存放到 List 中

<font size = 4,font color = blue> ColumnListHandler: </font> 将结果集中某一列的数据存放到 List 中

<font size = 4,font color = blue> KeyeHandler(name): </font> 将结果集中的每行数据都封装到 Map 里,再把这些 Map 再存到一个 Map 里,其 key 为指定的 key

<font size = 4,font color = blue> MapHandler: </font> 将结果集中的第一行数据封装到一个 Map 里,Key 是列名,Value 就是对应的值

<font size = 4,font color = blue> MapListHandler: </font> 将结果集中的每一行数据都封装到一个 Map 里,然后再存放到 List

按照之前的方式将 jar 包导入到编译器的 libs 包中

image-20240105143950011

右键 libs 包点击 Add as Library...

  • 代码演示:

JDBCUtils_Druid 工具类

package com.druidtest.fuxi;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/*
这是一个 JDBCUtils 工具类
 */
public class JDBCUtils_Druid {
    private static DataSource druid;
    static{
        try{
            Properties properties = new Properties();
            properties.load(new FileInputStream("src//druid.Properties"));
            druid = DruidDataSourceFactory.createDataSource(properties);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    public static Connection getConnection(){
        try{
            return druid.getConnection();
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    public static void close(Connection connection, ResultSet set, PreparedStatement set1){
        try{
            if(connection != null){
                connection.close();
            }
            if(set != null){
                set.close();
            }
            if(set1 != null){
                set1.close();
            }
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}

Actor 实体类:

package com.druidtest;
import java.time.LocalDateTime;
//Actor 对象对应 Demo 表
public class Actor {//Javabean,DoJO,Domain 对象
    // 定义表中对应的字段信息
    private Integer id;
    private String name;
    private String sex;
    private LocalDateTime borndata;
    private String phone;
    public Actor(){}// 一定要给一个无参构造器 [底层反射需要]
    // 创建一个有参构造用于获取字段的信息
    public Actor(Integer id, String name, String sex,LocalDateTime borndata, String phone) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndata = borndata;
        this.phone = phone;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public LocalDateTime getBorndata() {
        return borndata;
    }
    public void setBorndata(LocalDateTime borndata) {
        this.borndata = borndata;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    // 重写 toString 返回字符串类型的对象信息
    @Override
    public String toString() {
        return "牛马:{" +
                "牛马id=" + id +
                ", 牛马name='" + name + '\'' +
                ", 牛马sex='" + sex + '\'' +
                ", 牛马borndata=" + borndata +
                ", 牛马phone='" + phone + '\'' +
                '}'+"真Tm的牛马报错就TM离谱";
    }
}

测试类:

package comr.acx.dbutils;
import com.druidtest.Actor;
import com.druidtest.fuxi.JDBCUtils_Druid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.util.List;
public class DBUtils {
    // 使用 Apache-DBUtils 工具类 + Druid 完成对表的 crud 操作
    public static void main (String[]args) {
        //1. 得到连接 (Druid)
        Connection connection = null;
        try{
            connection = JDBCUtils_Druid.getConnection();
            //2. 使用 DBUtils 类和接口,先引入 DBUtils 相关的 jar, 加入到本 Project
            //3. 创建 QueryRunner
            QueryRunner queryrunner = new QueryRunner();
            // 编写 SQL 语句
            String sql = "select * from Demo where id >= ?";
            //4. 可以执行相关的方法,返回 ArrayList 结果集
            //connection: 连接
            //sql:SQL 语句
            //new BeanListHandler<>(Actor.class): 将 ResultSet -> Actor 对象 -> 封装到 ArrayList
            // 底层使用反射机制,去获取 Actor 类的属性,然后进行封装
            //49: 给 SQL 语句中的占位符 (?) 赋值,可以有多个值,因为是可变参数
            List<Actor> query = queryrunner.query(connection, sql, new BeanListHandler<Actor>(Actor.class),49);
            for(Actor i:query){
                System.out.println(i);
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //ResultSet,PreparedStatement query 底层已经帮我们关闭了所以就不用再次关闭了
            JDBCUtils_Druid.close(connection,null,null);
        }
    }
}

查询单行单列 : <font color = blue size = 4> ScalarHandler </font>

package comr.acx.dbutils;
import com.druidtest.fuxi.JDBCUtils_Druid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
public class DBUtils_Scalar {
    public static void main(String[]args){
        Connection connection = null;
        try{
            // 通过 JDBCUtils_Druid 调用 getConnection 方法获取连接
            connection = JDBCUtils_Druid.getConnection();
            // 创建 QueryRunner 实例
            QueryRunner queryrunner = new QueryRunner();
            // 返回单行单列,返回的就是 Object
            String sql = "select name from Demo where id = ?";
            // 查询单行单列的结果使用 ScalarHandler
            // 因为返回的是一个对象,使用 Handler 就是 ScalarHandler
            Object query = queryrunner.query(connection, sql, new ScalarHandler(), 49);
            System.out.println(query);
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //DBUtils 底层关闭了 ResultSet 和 PreparedStatement 只需要关闭 Connection
            JDBCUtils_Druid.close(connection,null,null);
        }
    }
}

执行 DML 语句 : <font size = 4 color = blue> update </font>(insert , delete , update)

package comr.acx.dbutils;
import com.druidtest.fuxi.JDBCUtils_Druid;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
public class DBUtils_DML {
    public static void main(String[]args){
        Connection connection = null;
        try{
            // 通过 JDBCUtils_Druid 调用 getConnection 方法获取连接
            connection = JDBCUtils_Druid.getConnection();
            // 创建 QueryRunner 实例
            QueryRunner queryrunner = new QueryRunner();
            // 编写 SQL 语句,并添加占位符
            String sql = "insert into Demo values (?,?,?,?,?)";
            // 通过 QueryRunner 对象调用 update 传入参数并填写对应的占位符的信息
            int i = queryrunner.update(connection,sql,null,"流桑","男","1998-12-11","111222000");
            System.out.println(i > 0 ? "添加成功" : "添加失败");
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            // 通过 JDBCUtils_Druid 调用 close 方法传入需要关闭的流对象
            JDBCUtils_Druid.close(connection,null,null);
        }
    }
}

Apache-DBUtils

  • 表和 JavaBean 的类型映射关系

image-20240105144004359

# DAO 和增删改查通用方法 - BasicDao

先分析一个问题

Apache-DBUtils+Druid 简化了 JDBC 开发,但还有不足

  1. SQL 语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行 <font color = red size = 4> 增删改查 </font>

  2. 对于 select 操作,如果有返回值,返回类型不能固定,需要使用泛型

  3. 将来的表很多,业务需求复杂,不可能只靠一个 Java 类完成

<font color = blue size = 4> 示意图:</font>

DAO 和增删改查通用方法 - BasicDAO

基本说明:

  1. DAO : data access object 访问数据对象
  2. 这样的通用类,称为 BasicDAO, 是专门和数据库交互的,即完成对数据库 (表) 的 crud 操作
  3. 在 BasicDAO 的基础上,实现一张表,对应一个 DAO, 更好的完成功能,比如 Customer 表 - Customer.java 类 (JavaBean)-CustomerDAO.java
  • 定义 JDBCUtils_Druid 工具类
package comr.acx.uti.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/**
 * @author Dkx
 */
public class JDBCUtils_Druid {
    /**
     * @comments 定义 DataSource 变量
     */
    private static DataSource druid;
    /**
     * @comments 定义静态代码块当类加载时加载这个方法执行 Properties 读取配置文件的信息
     */
    static {
        Properties properties = new Properties();
        try{
            properties.load(new FileInputStream("src//druid.Propeties"));
            druid = DruidDataSourceFactory.createDataSource(properties);
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * @comments 创建连接方法
     * @return 返回连接结果
     */
    public static Connection getConnection(){
        try{
            return druid.getConnection();
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * @comments 创建关闭需要关闭的流对象的方法
     * @param connection 关闭 Connection 对象流
     * @param set 关闭 ResultSet 对象流
     * @param statment 关闭 PreparedStatement 对象流
     */
    public static void close(Connection connection, ResultSet set, PreparedStatement statment){
        try{
            if(connection != null){
                connection.close();
            }
            if(set != null ){
                set.close();
            }
            if(statment != null){
                statment.close();
            }
        }catch(Exception e){
            throw new RuntimeException(e);
        }
    }
}
  • 定义 BasicDAO 类
package comr.acx.uti.dao;
import comr.acx.uti.utils.JDBCUtils_Druid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.util.List;
//BasicDAO 是其它 DAO 的父类
/**
 * @author Dkx
 * @param <T> 泛型
 */
public class BasicDAO<T> {
    private QueryRunner que = new QueryRunner();
    // 通用的 DML 方法,针对任意的表
    /**
     *
     * @param sql SQL 语句
     * @param parameters 为占位符赋值
     * @return 返回结果,返回执行成功的个数
     */
    public int DML (String sql,Object...parameters) {
        Connection connection = null;
        int i = 0;
        try{
            connection = JDBCUtils_Druid.getConnection();
            i = que.update(connection,sql,parameters);
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            JDBCUtils_Druid.close(connection,null,null);
        }
        return i;
    }
    // 返回多个对象 (即查询的结果是多行的), 针对任意的表
    /**
     *
     * @param sql SQL 语句,可以有占位符
     * @param clazz 传入一个类的 Class 对象,比如 Actor.class
     * @param parameters 传入占位符具体的值
     * @return 根据 Actor.class 类型返回 ArrayList 集合
     */
    public List<T> DQLtwo (String sql,Class<T> clazz,Object...parameters) {
        Connection connection = null;
        List<T> list = null;
        try{
            connection = JDBCUtils_Druid.getConnection();
            list = que.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            JDBCUtils_Druid.close(connection,null,null);
        }
        return list;
    }
    // 查询单行结果 (i)
    /**
     *
     * @param sql SQL 语句
     * @param clazz 传入一个类的 Class 对象,比如 Actor
     * @param parameters 给占位符赋值
     * @return 返回泛型结果
     */
    public T DQLSingle (String sql,Class<T> clazz,Object...parameters) {
        Connection connection = null;
        T i = null;
        try{
            connection = JDBCUtils_Druid.getConnection();
            i = que.query(connection,sql,new BeanHandler<T>(clazz),parameters);
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            JDBCUtils_Druid.close(connection,null,null);
        }
        return i;
    }
    // 查询单列结果 (一个)
    /**
     *
     * @param sql SQL 语句
     * @param parameters 为占位符赋值
     * @return 返回结果
     */
    public Object DQLUniseriate (String sql,Object...parameters) {
        Connection connection = null;
        Object query = null;
        try{
            connection = JDBCUtils_Druid.getConnection();
            query = que.query(connection, sql, new ScalarHandler(), parameters);
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            JDBCUtils_Druid.close(connection,null,null);
        }
        return query;
    }
}
  • 定义 Actor 实体类
package comr.acx.uti.domain;
import java.time.LocalDateTime;
/**
 * @author Dkx
 */
public class Actor {
    /**
     * 定义字段对应表的列类型
     */
    private Integer id;
    private String name;
    private String sex;
    private LocalDateTime borndata;
    private String phone;
    /**
     * 定义一个无参构造器初始化 [底层反射需要]
     */
    public Actor(){}
    /**
     * 定义有参构造器为字段进行赋值
     * @param id
     * @param name
     * @param sex
     * @param date
     * @param phone
     */
    public Actor(Integer id,String name,String sex,LocalDateTime date,String phone){
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.borndata = date;
        this.phone = phone;
    }
    /**
     *@Comment 定义 get,set 方法,方法的返回值类型和字段类型和名称都要对应表中的列数据不然获取不      * 到
     * @param id
     */
    public void setId(Integer id){
        this.id = id;
    }
    public Integer getId(){
        return id;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void setSex(String sex){
        this.sex = sex;
    }
    public String getSex(){
        return sex;
    }
    public void setPhone(String phone){
        this.phone = phone;
    }
    public String getPhone(){
        return phone;
    }
    /**
     *
     * @return 返回字符串类型的对象信息
     */
    @Override
    public String toString() {
    return "Actor{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", sex='" + sex + '\'' +
            ", borndata=" + borndata +
            ", phone='" + phone + '\'' +
            '}';
        }
}
  • 创建 ActorDAO 类继承 BasicDAO 类
package comr.acx.uti.dao;
import comr.acx.uti.domain.Actor;
/**
 * @author Dkx
 */
public class ActorDAO extends BasicDAO <Actor> {
    /**
     * 1. 因为 ActorDAO 继承了 BasicDAO 就有 BasicDAO 所有的方法,可以使用方法完成相应的操作
     * 2. 如果业务比较复杂也可以再 ActorDAO 中写特有的方法,根据业务需要可以编写特有的方法
     */
}
  • 测试类
public class ActorTest {
    public static void main(String[]args){
       // 创建 DAO 类
        ActorDAO dao = new ActorDAO();
       // 编写 SQL 语句
        String sql = "select * from user where username = ? and password = ?";
 		 // 通过 DAO 类对象名调用 QueryRunner.query (sql,clazz,parameter);
        List<Actor> actor = dao.DQLtwo(sql, Actor.class,"superbaby","123");
       // 遍历出查询借故
        for(Actor i : actor){
            System.out.println(i);
        }
    }
}

思路:

  • JDBCUtils_Druid 工具类

编写,读取配置文件方法,连接方法,关闭流方法

  • Actor 实体类

将从配置文件中读取到的信息赋值到字段中,解决 ResultSet 复用问题

  • BasicDAO 所有 DAO 的父类

定义操作方法,并定义 T 泛型因为不确定它的类型,它是所有 DAO 类的父类

  • ActorDAO 继承 BasicDAO 的子类

继承 BasicDAO 类,定义 BasicDAO 的泛型,可以根据业务需要在这个类中定义特有方法

  • TestActor 测试类

实例化 ActorDAO 即可编写 SQL 语句执行操作表 3