Skip to content

数据库相关

对于对数据库的操作,我们可以通过命令行终端(Dos)、可视化数据库软件进行操作,同时我们也可以通过Java进行对数据库的操作

通过Java操作Mysql

要求: 目的是为了体会通过Java发生SQL操作数据库、表

  1. 创建一个商品表(goods),选用适当的数据类型
  2. 添加1条数据
  3. 删除goods
java
public class JavaMysql {
    public static void main(String[] args) throws Exception {
        // 加载类,得到mysql连接
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_db01");
        // 编写sql语句  执行一条注释一条
        // String sql = "create table goods (id int, name varchar(32), price double)";
        // String sql = "insert into goods values(1, "西瓜", 10)";
        String sql = "drop table goods";
        // 得到statement对象,把sql语句发送给mysql执行
        Statement statement = connection.createStatement();
        statement.executeUpdate(sql);
        // 关闭连接
        statement.close();
        connection.close();
    }
}

JDBC

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

JDBC的基本原理图

image-20250504101850942

这里的接口就是JDBC,是一组规范,用于数据库操作的接口API,JAVA程序员只需要面向这套接口编程即可,不同的数据库厂商,需要针对这套接口,提供不同的实现

相关的类和接口在java.sqljavax.sql包中

JDBC程序编写的步骤:

  1. 注册驱动--加载Driver
  2. 获取连接--得到Connection(客户端(Java程序)到数据库的连接)
  3. 执行增删改查--发送SQL命令让mysql执行
  4. 释放资源--关闭相关的连接
java
// 一个简单的JDBC程序
public class Jdbc01 {
    public static void main(String[] args) {
        // 前置工作:在项目下创建一个文件夹,如libs,将mysql.jar拷贝到目录下,点击右键,选择add as library,加入到文件中
        // 1. 注册驱动
        Driver driver = new Driver();  // 创建driver对象
        // 2. 得到连接
        // jdbc:mysql:// 表示协议,是系统规定好的,通过jdbc的方式连接mysql
        // localhost表示主机,也可以是IP地址  3306表示mysql监听的端口  
        // my_mysql表示连接到mysql的哪个数据库
        String url = "jdbc:mysql://localhost:3306/my_mysql";
        // 将用户名和密码放入到Properties对象中
        Properties properties = new Properties();
        // user和password是规定好的,后面的值根据实际的情况写入
        properties.setProperty("user", "root");  // 指定用户名为root
        properties.setProperty("password", "abc");  // 指定用户名为abc
        // 得到连接
        Connection connect = driver.connect(url, properties);
        // 3. 执行sql语句
        String sql = "insert into stu value(1, 'jlc', '男', '25')";
        // statement用于执行静态的SQL语句并返回其生成的结果对象
        Statement statement = connect.createStatement();
        int rows = statement.executeUpdate(sql);  // 如果是dml语句,返回的是受到影响的行数
        System.out.println(rows > 0 ? "成功" : "失败");
        // 4. 关闭连接资源
        statement.close();
        connect.close();
    }
}

连接数据库的方式

Java程序获取数据库连接的五种方式:

  1. new一个Driver驱动对象,再通过driver.connect方法将url和用户信息传入,进行连接

    java
    Driver driver = new com.mysql.jdbc.Driver();  // 创建driver对象
    String url = "jdbc:mysql://localhost:3306/my_mysql";
    Properties properties = new Properties();
    properties.setProperty("user", "root");
    properties.setProperty("password", "abc");
    Connection connect = driver.connect(url, properties);

    该方式直接new了一个driver对象,这里的Driver是第三方的,且是一个静态加载,灵活性不够强,依赖性比较高

  2. 通过反射机制,进行动态的加载,可以将信息放入到配置文件中,更加灵活,减少依赖性,更利于项目的控制

    java
    Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver)aClass.newInstance();
    String url = "jdbc:mysql://localhost:3306/my_mysql";
    Properties properties = new Properties();
    properties.setProperty("user", "root");
    properties.setProperty("password", "abc");
    Connection connect = driver.connect(url, properties);
  3. 使用DriverManager(用于管理一组JDBC驱动程序的基本服务)来替换Driver进行统一管理

    java
    Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver)aClass.newInstance();
    String url = "jdbc:mysql://localhost:3306/my_mysql";
    String user = "root";
    String password = "abc";
    // 注册Driver驱动
    DriverManager.registerDriver(driver);
    Connection connection = DriverManager.getConnection(url, user, password);
  4. 使用Class.forName自动完成注册驱动,简化代码

    java
    Class.forName("com.mysql.jdbc.Driver");  // 在加载Driver类时,底层的静态代码块自动完成注册
    String url = "jdbc:mysql://localhost:3306/my_mysql";
    String user = "root";
    String password = "abc";
    Connection connection = DriverManager.getConnection(url, user, password);

    mysql驱动在5.1.6以后就无需使用Class.forName("com.mysql.jdbc.Driver"); ,也可以完成连接

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

    但是还是建议写上Class.forName("com.mysql.jdbc.Driver");,因为更加明确

  5. 在方式4的基础上,使用配置文件,连接数据库更加灵活(在开发的时候使用的最多)

    java
    // 通过Properties对象获取配置文件的信息
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\jdbc.properties"));
    String user = properties.getProperty("user");
    String password = properties.getProperty("password");
    String url = properties.getProperty("url");
    String driver = properties.getProperty("driver");
    
    Class.forName(driver);
    Connection connection = DriverManager.getConnection(url, user, password);
    properties
    # jdbc.properties 配置文件
    user=root
    password=abc
    url=jdbc:mysql://localhost:3306/my_mysql
    driver=com.mysql.jdbc.Driver

ResultSet

ResultSet表示数据库结果集的数据表,通常通过执行查询数据库的语句生成

ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前,next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集

使用演示:

java
public class ResultSet01 {
    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\jdbc.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
		// 注册驱动
        Class.forName(driver);
        // 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);
        // 得到Statement
        Statement statement = connection.createStatement();
        // 组织SQL语句
        String sql = "select id, name, sex, age from stu";
        // 执行给定的SQL语句,该语句返回单个的ResultSet对象
        ResultSet resultSet = statement.executeQuery(sql);
        // 使用while循环取出数据
        while(resultSet.next()) {  // 让光标向后移动,如果没有更多行,则返回false
            int id = resultSet.getInt(1);   // 获取该行的第一列
            // 也可以通过对应的列名获取 int id = resultSet.getInt("id");  
            String name = resultSet.getString(2);  // 获取该行的第二列
            String sex = resultSet.getString(3);
            int age = resultSet.getInt(4);
            System.out.println(id + "\t" + name + "\t" + sex + "\t" + age);
        }
        // 关闭连接
        resultSet.close();
        statement.close();
        connection.close();
    }
}

底层数据存储位置示意图:

image-20250504132649886

执行SQL语句

在连接建立后,需要对数据库进行访问,执行SQL语句,可以通过以下的方式:

  • StatementStatement是一个接口,其对象用于执行静态的SQL语句,并返回其生成的结果的对象

    存在SQL注入的问题,在实际的开发中使用的很少

    Statement对象执行SQL语句,存在SQL注入的风险(SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库)

    sql
    # 非法的SQL语句
    SELECT * FROM admin WHERE NAME = '1' OR' AND pwd = 'OR '1'='1'
    # 这时就会将用户信息查询出来,但是显然是非法的,通过OR,只要一个条件成立,就将用户信息数据返回

    要防范SQL注入的问题,只需要使用PreparedStatement(从Statement拓展而来)取代Statement即可

  • PreparedStatement:是Statement接口的子接口,可以使用Statement的方法,同时特有的预处理方式,可以有效的解决SQL注入风险的问题

    image-20250504135525004

    预处理方式,PreparedStatement执行的SQL语句中的参数用(?)来表示

    java
    String sql = "SELECT COUNT(*) FROM admin WHERE username = ? AND PASSWORD = ?";

    调用PreparedStatement对象的setXxx()方法来设置这些参数。setXxx()方法有两个参数,第一个参数是要设置SQL语句中的参数索引(从1开始),第二个参数是设置的SQL语句中的参数值

    预处理的好处:

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

    调用executeQuery(),返回ResultSet对象:

    java
    // 组织sql语句   语句中的?就相当于占位符
    String sql = "SELECT name, pwd FROM admin WHERE name = ? AND pwd = ?";
    PreparedStatement preparedStatement = connection.preparedStatement(sql);
    // 给sql语句中的?赋值
    preparedStatement.setString(1, "root");
    preparedStatement.setString(2, "abc");
    // 执行select语句使用executeQuery方法
    // 这里执行的时候,不能在写sql了,因为原先的sql已经被重置过了
    ResultSet resultSet = preparedStatement.executeQuery();

    调用executeUpdate(),执行更新,包括增、删、修改:

    java
    // 添加记录
    String sql = "insert into admin values(?, ?)";
    PreparedStatement preparedStatement = connection.preparedStatement(sql);
    preparedStatement.setString(1, "jlc");
    preparedStatement.setString(2, "123");
    int rows = preparedStatement.executeUpdate();  // 如果是dml语句,返回的是受到影响的行数
    System.out.println(rows > 0 ? "成功" : "失败");
    
    // 修改记录
    String sql = "update admin set pwd = ? where name = ?";
    PreparedStatement preparedStatement = connection.preparedStatement(sql);
    preparedStatement.setString(1, "789");
    preparedStatement.setString(2, "jlc");
    int rows = preparedStatement.executeUpdate();  // 如果是dml语句,返回的是受到影响的行数
    System.out.println(rows > 0 ? "成功" : "失败");
    
    // 删除记录
    String sql = "delete from admin where name = ?";
    PreparedStatement preparedStatement = connection.preparedStatement(sql);
    preparedStatement.setString(1, "jlc");
    int rows = preparedStatement.executeUpdate();  // 如果是dml语句,返回的是受到影响的行数
    System.out.println(rows > 0 ? "成功" : "失败");
  • CallableStatement:主要用于存储过程

JDBC常用API小结

image-20250504142409023

image-20250504142443782


封装JDBCUtils

JDBC操作中,获取连接和释放资源是经常被使用到的,因此,我们可以将其封装成JDBC连接的工具类JDBCUtils

image-20250504143605556

封装代码实现:

java
package com.test.jdbc.utils;

public class JDBCUtils {
    // 定义相关的属性(4个),因为只需要一份,我们使用静态的即可
    private static String user;  // 用户名
    private static String password;  // 密码
    private static String url;  // url
    private static String driver;  // 驱动名
    // 在static代码块中初始化
    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\mysql.properties"));
            // 获取相关的属性值
            user =  properties.getProperty("user");
            password =  properties.getProperty("password");
            url =  properties.getProperty("url");
            driver =  properties.getProperty("driver");
        } catch (IOException e) {
            // 在实际开发中,我们可以这样处理,将编译异常转换成运行异常进行抛出
            // 调用者可以选择捕获该异常,或者选择默认处理该异常,比较方便
            throw new RuntimeException(e);
        }
    }
    // 连接数据库,返回Connection
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    // 关闭相关资源
    /*
    	1. ResultSet 结果集
    	2. Statement 或者 PreparedStatement
    	3. Connection
    	4. 如果需要关闭资源,就传入对象,否则传入null
    */
    public static void close(ResultSet set, Statement statement, Connection connection){
        try {
            if(set != null) {
                set.close();
            }
            if(statement != null) {
                statement.close();
            }
            if(connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

封装代码的使用:

java
public class JDBCUtils_Use {
    public void testDML {  // insert, update, delete
        // 得到连接
        Connect connection = null;
        // 组织sql语句
        String sql = "insert into admin values(?, ?)";
        PreparedStatement preparedStatement = null;
        try {
            connection = JDBCUtils.getConnection();
			preparedStatement = connection.preparedStatement(sql);
            preparedStatement.setString(1, "jlc");
            preparedStatement.setString(2, "123");
            int rows = preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
    
    public void testSelect() {
        // 得到连接
        Connect connection = null;
        // 组织sql语句
        String sql = "select * from admin";
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        try {
            connection = JDBCUtils.getConnection();
			preparedStatement = connection.preparedStatement(sql);
            set = preparedStatement.executeQuery();
            while(set.next()) {
                String user = set.getString("user");
                String pwd = set.getString("pwd");
                System.out.println(user + "\t" + pwd);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.close(set, preparedStatement, connection);
        }
    }
}

事务

基本介绍:

  • JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务的,即每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚
  • JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
  • 调用ConnectionsetAutoCommit(false)可以取消自动提交事务
  • 在所有的SQL语句都成功执行后,调用Connectioncommit()方法进行提交
  • 在其中某个操作失败或出现异常时,调用Connectionrollback()方法回滚事务

使用事务来解决经典的转账问题:

java
public class Transaction_ {
    public void Transaction() {
        // 得到连接
        Connect connection = null;
        // 组织sql语句
        String sql1 = "update account set balance = balance - 100 where id = 1";
        String sql2 = "update account set balance = balance + 100 where id = 2";
        PreparedStatement preparedStatement = null;
        ResultSet set = null;
        try {
            connection = JDBCUtils.getConnection();
            // 将connection设置为不自动提交
            connection.setAutoCommit(false);  // 开启事务
			preparedStatement = connection.preparedStatement(sql1);
            preparedStatement.executeUpdate();  // 执行第一条sql语句
            preparedStatement = connection.preparedStatement(sql2);
            preparedStatement.executeUpdate();  // 执行第二条sql语句
            // 提交事务
            connection.commit();
        } catch (SQLException e) {
            System.out.println("执行发生了异常,撤销执行的sql");
            try {
                // 出现异常,我们进行回滚,即撤销执行之前运行的sql语句
            	connection.rollback();  // 回滚到事务开启的时候
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.close(null, preparedStatement, connection);
        }
    }
}

批处理

基本介绍:

  • 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,允许多条语句一次性提交给数据库批量处理,比单独提交处理更有效率
  • JDBC的批量处理语句包括:
    • addBatch():添加需要批量处理的SQL语句或参数
    • executeBatch():执行批量处理语句
    • clearBatch():清空批处理包的语句(对于数据量特别大的情况,需要运送一批,清空后再运输)
  • JDBC连接mysql时,如果要使用批处理功能,请在url中加参数?rewritBatchedStatements=true
  • 批处理往往和PreparedStatement一起搭配使用,既可以减少编译次数,又减少运行次数,效率大大提高

演示案例:向admin表中使用批处理的方式添加5000条数据

properties
# jdbc.properties 配置文件  要使用批处理的方式,要加上?rewritBatchedStatements=true
user=root
password=abc
url=jdbc:mysql://localhost:3306/my_mysql?rewritBatchedStatements=true
driver=com.mysql.jdbc.Driver
java
public class Batch_ {
    public void Batch() throws Exception {
        Connect connection = JDBCUtils.getConnection();
        String sql = "insert into admin values(?, ?)";
        PreparedStatement preparedStatement = connection.preparedStatement(sql);
        for(int i = 0; i < 5000; i++) {
            preparedStatement.setString(1, "jlc" + i);
            preparedStatement.setString(2, "666");
            // 将sql语句加入到批处理包中
            preparedStatement.addBatch();
           	// 当有1000条记录时,批量执行
            if((i + 1) % 1000 == 0) {
                preparedStatement.executeBatch();
                // 清空处理包语句
                preparedStatement.clearBatch();
            }
        }
        // 关闭连接
        JDBCUtils.close(null, preparedStatement, connection);
    }
}

addBatch()的源码解析:

java
public void addBatch() throws SQLException {
    synchronized(this,checkClosed().getConnectionMutex()) {\
        // 第一次创建ArrayList了的对象数组elementData(Object[])来存放预处理的sql语句
        // elementData数组的默认大小是10,当其满后,就按照1.5倍进行扩容
        // 当添加到指定数量的值后,就executeBatch
        // 批量处理会减小我们发送sql语句的网络开销,且减少编译次数,因此效率比较高
        if (this.batchedArgs == null) {
            this.batchedArgs = new ArrayList();
        }
        for (int i = 0; i < this.parameterValues.length; ++i) {
            this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
        }
        this.batchedArgs.add(new PrepardStatement.BatchParams(this.parameterValues, this.parameterStreams))
    }
}

数据库连接池

使用传统方式进行获取连接的问题分析:

  • 对于传统的方式来获取连接,使用的是JDBC中的DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址、用户名和密码(耗时0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器奔溃
  • 每一次数据量的连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库的内存泄漏,最终将导致重启数据库
  • 传统获取连接的方式,不能控制创建的连接数量,如果连接过多,可能会导致内存泄漏,导致mysql奔溃

解决传统开发中的数据库连接问题,可以采用数据库连接池技术

数据库连接池的基本介绍:

  1. 预先在缓冲池中放入一定数量的连接,当需要建立数据连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去
  2. 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  3. 当应用程序向连接池请求的连接数量超过最大连接数量时,这些请求将被加入到等待队列中

数据库连接池示意图:

image-20250504223648848

数据库连接池的种类:

  • JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
  • C3P0数据库连接池,速度相对较慢,稳定性不错(用的比较多)
  • DBCP数据库连接池,速度相对C3P0较快,但不稳定
  • Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点
  • BoneCP数据库连接池,速度快
  • Druid(德鲁伊)是阿里提供的数据库连接池,集DBCPC3P0Proxool优点于一身的数据库连接池(用的最多)

C3P0

使用C3P0时,我们先要加载C3P0对应的jar包,将包引入到我们的项目中

C3P0的使用演示:

方式一:相关参数userurlpassword等,在程序中指定

java
public class C3P0_ {
    public void testC3P0_01() {
        // 1. 创建一个数据源对象
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        // 2. 通过mysql.properties 获取相关的配置信息
        // 通过Properties对象获取配置文件的信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        // 给数据源设置相关的参数  连接的管理是由comboPooledDataSource来管理的
        comboPooledDataSource.setDriverClass(driver);
        comboPooledDataSource.setJdbcUrl(url);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        // 设置初始化的连接数,程序运行时,连接池中会存放设置数量的已经与数据库连接好的连接
        comboPooledDataSource.setInitialPoolSize(10);
        // 设置连接池的最大连接数,如果最大连接数的连接都被使用了,那么后续的连接请求会进入到等待队列中
        comboPooledDataSource.setMaxPoolSize(50);
        // 测试连接池的效率,对mysql连接500000次操作
        long start = System.currentTimeMillis();
        for(int i = 0; i < 500000; i++) {
            // 获取连接  对应方法是从DataSource接口实现的
            Connect connection = comboPooledDataSource.getConnection();
            // 3. 关闭连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - start));
    }
}
properties
# mysql.properties 配置文件
user=root
password=abc
url=jdbc:mysql://localhost:3306/my_mysql
driver=com.mysql.jdbc.Driver

方式二:使用配置文件模板来完成(推荐使用),将C3P0提供的c3p0.config.xml配置文件模板(该文件指定了连接数据库和连接池的相关参数)拷贝到src目录下,c3p0.config.xml配置文件模板的基本内容如下:

xml
<c3p0-config>
    <!--使用默认的配置读取数据库连接池对象 -->
    <default-config name="hello">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/my_mysql?useServerPrepStmts=true</property>
        <property name="user">root</property>
        <property name="password">abc</property>

        <!-- 连接池参数 -->
        <!--每次增长的连接数-->
        <property name="acquireIncrement">5</property>
        <!--初始化申请的连接数量-->
        <property name="initialPoolSize">10</property>
        <!--最小的连接数量  如果在一定时间内几乎没有程序进行连接,那么就会关闭连接数到最小的数量-->
        <property name="minPoolSize">5</property>
        <!--最大的连接数量-->
        <property name="maxPoolSize">50</property>
        
        <!--可连接的最多的命令对象数-->
        <property name="maxStatements">5</property>
        <!--每个连接对象可连接的最多的命令对象数-->
        <property name="maxStatementsPerConnection">2</property>
    </default-config>
</c3p0-config>
java
public class C3P0_ {
    public void testC3P0_02() {
        // 1. 创建一个数据源对象,读取c3p0.config.xml配置文件,其name为hello的配置信息
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");
        // 测试连接池的效率,对mysql连接500000次操作
        long start = System.currentTimeMillis();
        for(int i = 0; i < 500000; i++) {
            // 2. 获取连接  对应方法是从DataSource接口实现的
            Connect connection = comboPooledDataSource.getConnection();
            // 3. 关闭连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - start));
    }
}

Druid

使用Druid数据库连接池,也是遵守连接池的基本原理的,该方式的连接池获取连接和关闭连接的效率是最高的

演示Druid连接池的使用:使用Druid时,我们先要加载Druid对应的jar包,将包引入到我们的项目中,同时加入Druid连接池的配置文件(拷贝到src目录即可,名称可以自定义,这里命名为druid.properties),该配置文件内容如下:

properties
#key=value
driverClassName=com.mysql.jdbc.Driver
#URL连接数据库的URL
url=jdbc:mysql://localhost:3306/my_mysql?rewriteBatchedStatements=true
username=root
password=abc
#初始化物理连接的个数
initialSize=10
#最小连接池数量
minIdle=5
#最大连接池数量
maxActive=50
#获取连接时最大等待时间(在等待队列中最长等待的时间,如果超过这个时间,就放弃这次连接)
maxWait=5000
java
public class Druid_ {
    public void testDruid() throws Exception {
        // 创建Properties对象,读取配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\druid.properties"));
        // 创建一个指定参数的数据库连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        
        // 测试连接池的效率,对mysql连接500000次操作
        long start = System.currentTimeMillis();
        for(int i = 0; i < 500000; i++) {
            // 获取连接
       		Connection connection = dataSource.getConnection();
            // 关闭连接
            connection.close();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - start));
    }
}
Druid工具类

我们一般也要将Druid进行封装(将获取连接和关闭连接作为方法封装起来),变成一个工具类

java
public class JDBCUtilsByDruid {
    private static DataSource ds;
    // 在静态代码块中完成初始化(只在加载类的时候,会初始化一次)
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 编写getConnection方法
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    // 关闭连接,在数据库连接池中,close不是断掉与数据库的连接,而是把使用的Connection对象放回连接池中
    /*
    	1. ResultSet 结果集
    	2. Statement 或者 PreparedStatement
    	3. Connection
    	4. 如果需要关闭资源,就传入对象,否则传入null
    */
    public static void close(ResultSet set, Statement statement, Connection connection){
        try {
            if(set != null) {
                set.close();
            }
            if(statement != null) {
                statement.close();
            }
            if(connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

封装工具类的测试:

java
public class Druid_ {
    public void testDruid() throws Exception {
        // 测试连接池的效率,对mysql连接500000次操作
        long start = System.currentTimeMillis();
        for(int i = 0; i < 500000; i++) {
            // 获取连接
       		Connection connection = JDBCUtilsByDruid.getConnection();
            // 关闭连接
            JDBCUtilsByDruid.close();
        }
        long end = System.currentTimeMillis(null, null, connection);
        System.out.println("耗时" + (end - start));
    }
}

ApDBUtils

ApDBUtils的基本引出和具体实现示意图:

image-20250505102906733

最后将数据保存到了ArrayList<Actor>实例化出的集合中,后续断掉连接与数据库的连接,数据还是存在的(ArrayListconnection没有任何关联),还是可以进行复用

基本介绍:

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

DBUtils类:

  • QueryRunner类:封装了SQL的执行,是线程安全的,可以实现增删改查,批处理

  • ResultSetHandler接口:用于处理java.sql.ResultSet,将数据按照要求转为另一种形式,常用的有:

    image-20250505104210955

image-20250505113457179

基本使用

案例演示:使用DBUtils+数据连接池(德鲁伊)方式,完成对表actor的增删改查:

使用DBUtils前需要引入相关的jar包,在加入到本项目中

多行查询
java
public class DBUtils_USE {
    // 查询,返回的结果是多行的情况
    public void testQueryMany() throws SQLException {
        // 通过封装的德鲁伊方法,得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 执行相关方法,返回ArrayList结果集
        // query方法就是执行sql语句,得到resultset结果集,将其封装到ArrayList集合中,再返回
        // connection:连接;sql:执行的sql语句;
        // new BeanListHandler<>(Actor.class):将结果集取出Actor对象,封装到ArrayList中,底层使用了反射机制,去获取Actor类的属性,然后再进行封装
        // 最后的1,是传递给sql语句中的?赋值,可以有多个值,因为是可变参数
        String sql = "select * from actor where id >= ?";
        List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
        // 输出集合的信息
        for(Actor actor: list) {
            System.out.print(actor);
        }
        // 释放资源  底层得到的resultset和PreparedStatment,会在query中关闭
        JDBCUtilsByDruid.close(null, null, connection);
    }
}

sql语句也可以查询部分列

select id, name from actor where id >= ?

那么,返回的结果对于没有指定查询的字段返回的内容是null

单行查询
java
public class DBUtils_USE {
    // 查询,返回的结果是单行的情况
    public void testQuerySingle() throws SQLException {
        // 通过封装的德鲁伊方法,得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 执行相关方法,返回单个对象
        // 因为返回的是单行记录,即单个对象,使用的Hander是BeanHandler
        String sql = "select * from actor where id = ?";
        Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
        // 输出信息
        System.out.print(actor);
        // 释放资源  底层得到的resultset和PreparedStatment,会在query中关闭
        JDBCUtilsByDruid.close(null, null, connection);
    }
}

如果查询的结果不存在,返回的内容是null

如果要查询的结果是单行单列的情况,返回的就是一个Object对象的形式

java
public class DBUtils_USE {
    // 查询,返回的结果是单行的情况
    public void testQueryScalar() throws SQLException {
        // 通过封装的德鲁伊方法,得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 执行相关方法,返回单个对象
        // 因为返回的是单行记录,即单个对象,使用的Hander是BeanHandler
        String sql = "select name from actor where id = ?";
        Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 1);
        // 输出信息
        System.out.print(obj);
        // 释放资源  底层得到的resultset和PreparedStatment,会在query中关闭
        JDBCUtilsByDruid.close(null, null, connection);
    }
}

如果查询的结果不存在,返回的内容是null

增删改操作
java
public class DBUtils_USE {
    // dml操作
    public void testDML() throws SQLException {
        // 通过封装的德鲁伊方法,得到连接
        Connection connection = JDBCUtilsByDruid.getConnection();
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        // 组织sql语句,完成update、insert和delete
        // 返回的affectedRow是受影响的行数,1表示sql语句执行成功,0表示执行失败
        
        // 增
        String sql = "insert into actor values(null, ?, ?)";
        int affectedRow = queryRunner.update(connection, sql, "jlc", "男"); 
        
        // 删
        String sql = "delete from actor where id = ?";
        int affectedRow = queryRunner.update(connection, sql, 1); 
        
        // 改
        String sql = "update actor set name = ? where id = ?";
        int affectedRow = queryRunner.update(connection, sql, "jlc", 1); 
        
        
        System.out.print(affectedRow > 0 ? "执行成功" : "执行失败");
        // 释放资源  底层得到的resultset和PreparedStatment,会在query中关闭
        JDBCUtilsByDruid.close(null, null, connection);
    }
}

BasicDao

apache-dbutils+Druid简化了JDBC的开发,但是还有不足:

  • SQL语句固定(如,所查的表固定),不能通过参数传入,通用性不好,需要进行改进,使执行更方便
  • 对于select操作,如果有返回值,返回值类型不能固定,需要使用泛型
  • 将来的表很多,业务需求复杂,不可能只靠一个Java类完成

在实际的开发中,一般需要使用BasicDao进行优化,体现了各司其职的思想

image-20250505124519515

在实际开发中,一张数据表对应一个DAO,该DAO完成对这张表的增删改查操作,对应共有的操作,我们将其放到BasicDao中,简化代码,提高了代码的维护性和可读性,同时每一张数据表与Java中的某一个类有映射关系

基本说明:

  1. DAOdata access object数据访问对象
  2. 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作
  3. BasicDao基础上,实现一张表对应一个Dao,更好的完成功能,如Customer表--Customer.java类(javabean)--CustomerDao.java

设计实例:演示一套Actor的流程,其他数据表类似

  • com.test.dao_.utils:相关工具类

    java
    package com.test.dao_.utils;
    
    public class JDBCUtilsByDruid {
        private static DataSource ds;
        // 在静态代码块中完成初始化(只在加载类的时候,会初始化一次)
        static {
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("src\\druid.properties"));
                ds = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 编写getConnection方法
        public static Connection getConnection() throws SQLException {
            return ds.getConnection();
        }
        // 关闭连接,在数据库连接池中,close不是断掉与数据库的连接,而是把使用的Connection对象放回连接池中
        /*
        	1. ResultSet 结果集
        	2. Statement 或者 PreparedStatement
        	3. Connection
        	4. 如果需要关闭资源,就传入对象,否则传入null
        */
        public static void close(ResultSet set, Statement statement, Connection connection){
            try {
                if(set != null) {
                    set.close();
                }
                if(statement != null) {
                    statement.close();
                }
                if(connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
  • com.test.dao_.domainJavabean具体对应数据表的Java

    java
    package com.test.dao_.domain;
    import java.util.Date;
    
    public class Actor {
        private Integer id;
        private String name;
        private String sex;
        private Date borndate;
        private String phone;
        // 一定要给一个无参构造器,用于反射
        public Actor() {}
        public Actor(Integer id, String name, String sex, Date borndate, String phone) {
            this.id = id;
            this.name = name;
            this.sex = sex;
            this.borndate = borndate;
            this.phone = phone;
        }
        fuction getName() {
            return name;
        }
        ...
    }
  • com.test.dao_.dao:存放XxxDAOBasicDAO

    先开发BasicDAO(是其他DAO的一个父类)

    java
    package com.test.dao_.dao;
    import org.apache.commons.dbutils.QueryRunner;
    
    public class BasicDAO<T> {  // 通过泛型指定类型
        private QueryRunner qr = new QueryRunner();
        // 开发通用的dml方法,针对任意的表
        public int update(String sql, Object... parameters) { // parameters是可变参数
            Connection connection = null;
            try {
                // 获取连接
                connection = JDBCUtilsByDruid.getConnection();
                // sql语句
                int update = qr.update(connection, sql, parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                // 关闭连接
                JDBCUtilsByDruid.close(null, null, connection);
            }
        }
        // 开发返回多行的查询结果,针对任意表
        // sql是SQL语句,可以有?  clazz传入一个Class对象,比如Actor.class
        // parameters传入?的具体的值,可以传入多个,是一个可变参数
        // 最后返回根据Actor.class对应的ArrayList集合
        public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
            Connection connection = null;
            try {
                // 获取连接
                connection = JDBCUtilsByDruid.getConnection();
                // sql语句
                return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                // 关闭连接
                JDBCUtilsByDruid.close(null, null, connection);
            }
        }
        // 开发查询单行的结果,针对任意表
        public T querySingle(String sql, Class<T> clazz, Object... parameters) {
            Connection connection = null;
            try {
                // 获取连接
                connection = JDBCUtilsByDruid.getConnection();
                // sql语句
                return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                // 关闭连接
                JDBCUtilsByDruid.close(null, null, connection);
            }
        }
        // 查询单行单列的方法,即返回单值的方法
        public Object queryScalar(String sql, Object... parameters) {
            Connection connection = null;
            try {
                // 获取连接
                connection = JDBCUtilsByDruid.getConnection();
                // sql语句
                return qr.query(connection, sql, new ScalarHandler(), parameters);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                // 关闭连接
                JDBCUtilsByDruid.close(null, null, connection);
            }
        }
    }

    开发ActorDAO:继承BasicDAO

    java
    package com.test.dao_.dao;
    import com.test.dao_.domain.Actor;
    
    public class ActorDAO extends BasicDAO<Actor> {
        // 有BasicDAO所有的方法
        // 还可以根据需求,写特有的方法
    }
  • com.test.dao_.test:存放测试类

    java
    package com.test.dao_.test;
    
    public class TestDAO {
        // 测试ActorDAO 对 actor 表的crud操作
        public void testActorDAO() {
            ActorDAO actorDAO = new ActorDAO();
            // 查询多行
            List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
            for (Actor actor: actors) {
                System.out.println(actor);
            }
            // 查询单行
            Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 1);
            System.out.println(actor);
            // 查询单行单列
            Object o actorDAO.querySingle("select name from actor where id = ?", 1);
            System.out.println(o);
            // dml操作
            String sql = "update actor set name = ? where id = ?";
            int update = actorDAO.update(connection, sql, "jlc", 1); 
            System.out.print(affectedRow > 0 ? "执行成功" : "执行失败");
        }
    }

Released under the MIT License.