<Java>23 JDBC 和数据库连接池

本文最后更新于:2022年5月24日 早上

23 JDBC 和数据库连接池

JDBC:Java 提供的一套用于操作数据库的接口 API。Java 程序员只需要面向该接口即可连接任何提供了 JDBC 驱动程序的数据库,完成对数据库的各种操作。不同的数据库厂商,需要针对这套接口提供不同的实现。

JDBC 程序编写步骤(示范):

	/* [0] 前置操作 */
	/* [1] 注册驱动 */
Driver driver = new com.mysql.cj.jdbc.Driver();
	/* [2] 获取连接 */
String url = "jdbc:mysql://localhost:3306/melody";
Properties properties = new Properties();				
properties.setProperty("user", "root");
properties.setProperty("password", "******");
Connection connect = driver.connect(url, properties);	
	/* [3] 执行语句 */
String sql = "insert into customer (customer_id,name,card_id) values(0004,'赫尔','00000000000000000O')";
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);
System.out.println(rows);
	/* [4] 释放资源 */
statement.close();
connect.close();
  1. 前置操作:将 mysql-connector-java-8.0.27.jar 该文件拷贝到项目目录下,选中后右键选择 Add as library

  2. 注册驱动:加载 Driver

    这里我导入了 com.mysql.cj.jdbc.Driver。根据前面的 jar 包的版本不同,应该导入的路径也会不同。

    旧版本的 jar 包可能应该导入 com.mysql.jdbc.Driver 这个包。

  3. 获取连接:得到 Connection

    String url = "jdbc:mysql://localhost:3306/melody";

    这里表示进入 melody 这个数据库。

  4. 执行语句:发送 SQL 命令给 MySQL 执行

    int rows = statement.executeUpdate(sql);

    如果是 dml 语句,这里返回的 rows 是影响的行数。返回 0 表示失败。

  5. 释放资源:关闭相关连接**(切记!)**

23.1 连接数据库的 5 种方式

  • 方式 1:直接创建 Driver,调用 driver.connet(url, properties);

    这个方法就是 [23.0] 示范的方法

    Driver driver = new com.mysql.cj.jdbc.Driver();
    String url = "jdbc:mysql://localhost:3306/melody";
    Properties properties = new Properties();				
    properties.setProperty("user", "root");
    properties.setProperty("password", "******");
    Connection connect = driver.connect(url, properties);
  • 方式 2:使用反射加载 Driver

    这个方式是动态加载,更加灵活。

    Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
    Driver driver = (Driver) aClass.newInstance();
    Properties properties = new Properties();				
    properties.setProperty("user", "root");
    properties.setProperty("password", "******");
    Connection connect = driver.connect(url, properties);
  • 方式 3:使用 DriverManager 替代 Driver 进行统一管理

    这个方式扩展性更好,更加清晰

    Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
    Driver driver = (Driver) aClass.newInstance();
    String url = "jdbc:mysql://localhost:3306/melody";
    String name = "root";
    String password = "******";
    DriverManager.registerDriver(driver);
    Connection connection = DriverManager.getConnection(url, name, password);

    这里,DriverManager.getConnetion() 有三种方法。分别是:

    • getConnetion(url);
    • getConnetion(url, properties);
    • getConnetion(url, name, password);
  • 方式 4:使用 Class.forName() 自动完成注册驱动

    Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/melody";
    String name = "root";
    String password = "******";
    Connection connection = DriverManager.getConnection(url, name, password);

    Class.forName() 在加载 Driver 类时,会自动完成注册。

    原理:

    Driver 类的源码中有这样一段

    static {
         try {
             DriverManager.registerDriver(new Driver());
         } catch (SQLException var1) {
             throw new RuntimeException("Can't register driver!");
         }
    }

    这个静态代码块在类加载时会被执行。

    另外,其实不执行 Class.forName(...); 语句的场合,也能正确获取 Connection

    这是因为 JDK 1.5 以后使用了 jdbc4。这个场合,系统会自动调用 jar 包下 META-INF\services\java.sql.Driver 这个文件中的类名称去注册。

    ……打开上述文件看看,里面赫然写着:com.mysql.cj.jdbc.Driver

    即使如此,还是建议写上 Class.forName(...) 语句!

  • 方式 5(推荐):在方式 4 的基础上,使用配置文件,连接数据库更灵活。

    这种方式是实际开发最常用的方式。

    配置文件(创建 src\mysql.properties):

    url=jdbc:mysql://localhost:3306/melody
    user=root
    password=******
    driver=com.mysql.cj.jdbc.Driver

    代码:

    Properties pro = new Properties();
    pro.load(new FileInputStream("src\\mysql.properties"))
    String url = pro.getProperties("url");
    String user = pro.getProperties("user");
    String password = pro.getProperties("password");
    String driver = pro.getProperties("driver");
    Class<?> aClass = Class.forName(driver);
    Connection connection = DriverManager.getConnection(url, user, password);

23.2 ReaultSet 结果集

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

ResultSet 对象保持一个光标指向其当前的数据行。该光标的初始位置在第一行之前。调用 next 方法将光标下移,移动到末端的场合会返回 false。

如何取出数据(示例):

String sql = "select * from customer";
ResultSet resset = statement.executeQuery(sql);
while (resset.next()) {
    int id = resset.getInt(1);
    String name = resset.getString(2);
    String sex = resset.getString(5);
    String card_id = resset.getString(6);
    System.out.println(id + "\t" + name + "\t" + sex + "\t" + card_id);
}

其中 String sex = resset.getString(5); 表示取出该行数据的第 5 列的字符串数据。

ResultSet 的数据是以类似二维数组的形式保存在对象中。这不难理解,毕竟表格是二维的嘛。

23.3 Statement

Statment 对象用于执行静态 SQL 语句,并返回其生成的结果的对象

在连接建立后,需要访问数据库、执行命名或 SQL 语句,有如下方式:

  • Statment(存在 SQL 注入问题,在实际开发中基本不使用)
  • PerparedStatement(预处理)
  • CallableStatement(存储过程)

SQL 注入:利用某些系统没有对用户输入的数据进行充分的检查,故意注入非法的 SQL 语句段或命令,恶意攻击数据库。

使用 PreparedStatement 可以防范 SQL 注入

23.3.1 PreparedStatement 预处理

	/* [1] */
String sql = "select * from customer where customer_id = ? and name = ?";
PreparedStatement ps = connection.prepareStatement(sql);
	/* [2] */
ps.setInt(1, 0);
ps.setString(2, "萝茵");
	/* [3] */
ResultSet rs = ps.executeQuery();
  1. PreparedStatement 执行的 SQL 语句的参数用 ? 表示。

  2. 调用方法设置 SQL 语句中的参数。

    这些方法的两个参数中,第一个是设置的参数的索引,第二个是设置值

  3. 调用 executeQuery() 返回 ResultSet 对象。或者调用 executeUpdate() 执行增删改

    这里,调用的方法不需要再传入参数

预处理的好处

  1. 不再使用拼接语句,增加代码可读性,减少语法错误
  2. 解决了 SQL 注入问题
  3. 大大减少了编译次数,效率提高
类 / 接口 方法
DriverManager 驱动管理类 getConnection(url, user, pwd) 获取连接
Connection 接口 createStatement() 创建 Statement 对象
preparedStatement(sql) 生成预处理 PreparedStatement 对象
Statement 接口 executeUpdate(sql) 执行 dml 语句,返回影响行数
executeQuery(sql) 执行查询语句,返回 ResultSet
execute(sql) 执行任意 SQL 语句,返回布尔值
PreparedStatement 接口 executeUpdate() 执行 dml 语句,返回影响行数
executeQuery() 执行查询语句,返回 ResultSet
execute() 执行任意 SQL 语句,返回布尔值
setXXX(int, xxx) 设置 SQL 语句中占位符的值
setObject(int, xxx) 设置 SQL 语句中占位符的值
ResultSet 结果集 next() 向下移动一行。没有下一行的场合返回 false
previous() 向上移动一行。没有上一行的场合返回 false
getXXX(int)、getXXX(name) 返回 int 列 / name 列的值
getObject(int)、getObject(name) 返回 int 列 / name 列的值

23.4 JDBCUtils

实际操作中,获取连接 和 释放资源 操作经常使用。可以把这些操作封装成工具类 JDBCUtils

JDCUtils.java

package com.melody;

import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

/**
 * @author Melody
 * @version 1.0
 */
public class JDBCUtils {
    private static String url;
    private static String user;
    private static String pw;
    private static String driver;

    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src/com/melody/mysql.properties"));
            url = properties.getProperty("url");
            user = properties.getProperty("user");
            pw = properties.getProperty("password");
            driver = properties.getProperty("driver");
            Class<?> aClass = Class.forName(driver);
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);					//[1]
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, pw);
    }

    public static boolean close(Connection c, Statement s, ResultSet r) {
        try {
            if (!(c == null)){
                c.close();
            }
            if (!(s == null)){
                s.close();
            }
            if (!(r == null)){
                r.close();
            }
            return true;
        } catch (SQLException e) {
            throw new RuntimeException(e);					//[1]
        }
    }
}
  1. 实际开发中,可以把编译异常转换为运行异常。这样,调用者可以自行选择捕获异常还是默认处理。

23.5 事务

事务:JDBC 程序中,当一个 Connection 对象创建时,默认情况下会自动提交事务。为了让多个 SQL 语句一体执行,需要使用事务。

Connection connection = null;
try {
    connection = JDBCUtils.getConnection();
    connection.setAutoCommit(false);					//[1] 取消自动提交事务
    String sql = "insert into actor values(9, '赫尔萝茵')";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    preparedStatement.executeUpdate();
    sql = "delete from actor where name = '萝茵' and id > (select * from (select min(id) from actor where name = '萝茵') a)";
    													//[2]
    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.executeUpdate();
    connection.commit();								//[3] 提交事务
    System.out.println("complete");
} catch (SQLException e) {
    System.out.println("fail");
    System.out.println(e);
    connection.rollback();								//[4] 回滚事务
} finally {
    connection.close();									//[5] 关闭连接
}
  1. 取消自动提交事务:

    connection.setAutoCommit(false);
  2. 这是一个 SQL 语句。表示在 name = 萝茵 的数据中只保留 id 最小的一条。

    delete from actor where name = '萝茵' and id > (select * from (select min(id) from actor where name = '萝茵') a)

    正常写法会提示不能同表查询(在 MySQL 中,不能在同一语句中先 select 出同一表中的某些值,再 update 这个表)。使用一个额外的 select 过渡就解决了这个问题。特此记录。

  3. 提交事务:

    connection.commit();
  4. 回滚事务:

    connection.rollback();

    写在 catch 里。这样,语句错误抛出异常的场合会执行到这句话。

  5. 关闭连接:

    connection.close();

    写在 finally 里。这样,执行完语句总会关闭连接。这很好。

23.6 批处理

批处理:把多条语句一次性提交给数据库进行批量处理。这样做比单独提交更有效率。

要使用批处理功能,需要在 url 中加入 ?rewriteBatchedStatements=true,即在配置文件中写成:

url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true

批处理往往和 PerparedStatement 搭配使用,既减少编译次数,又减少运行次数,岂不美哉?

示例:

	//[0] 事前准备
Connection connection = JDBCUtils.getConnection();
String sql = "insert into test1 values(?)";
PreparedStatement ps = connection.prepareStatement(sql);
for (int i = 0; i < 1000; i++) {
	ps.setInt(1,i);						
	ps.addBatch();						//[1]
}
ps.executeBatch();						//[2]
JDBCUtils.close(connection, ps, null);
  1. 修改配置文件

    url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
    ...
  2. 添加需要批量处理的 SQL 语句

    preparedStatement.addBatch();
  3. 执行批量处理语句

    preparedStatement.executeBatch();
  4. (前面的例子里没有这句)清空批处理包的语句

    preparedStatement.clearBatch();

23.6.1 说明

  1. 第一次添加批处理语句时,创建一个 ArrayList。

    ...
    if (this.batchedArgs == null) {
        this.batchedArgs = new ArrayList();
    }
    ...

    ……会在其中放置批处理语句(指令的数据)

  2. 该数组满后,按照 1.5 倍扩容

  3. 达到指定的值后,执行 executeBatch 吧

  4. 批处理能减少编译次数,也能减少发送 SQL 语句的网络开销。

23.7 数据库连接池

传统获取 Connection 方法的问题:

  1. 传统的 JDBC 数据库连接使用 DriverManager 获取,每次建立连接都会把 Connection 载入内存,再进行身份验证。每次连接都会重复验证请求,这样会占用过多系统资源,容易造成服务器崩溃。
  2. 每次连接,使用完毕后必须断开。如果不断开操作,会致使数据库内存泄漏,最终不得不重启数据库。
  3. 传统连接方式不能控制创建的连接数量。连接数量过多的场合,也可能导致内存泄漏,MySQL 崩溃

因此,需要使用连接池技术:

  1. 预先在缓冲池放入一定数量的连接。需要建立数据库连接时,从缓冲池中取出一个连接。使用完后,把该连接放回缓冲池。
  2. 数据库连接池负责分配、管理和释放数据库连接。其允许应用程序重复使用一个现有的数据库连接,而非建立新的连接。
  3. 当请求连接的数量超过最大数量,这些连接请求会被加入等待队列。

数据库连接池种类:

JDBC 数据库连接池使用 javax.sql.DataSource 表示。DataSource 是一个接口,通常由第三方提供实现(提供 jar 包)

  • C3P0:速度稍慢,稳定性好……但是速度再慢也不是传统 JDBC 能比得上的,不自量力!
  • DBCP:速度稍快,稳定性差
  • Proxool:可以监控连接池状态,稳定性稍差
  • BoneCP:速度快
  • Druid:阿里提供的数据库连接池,集 DBCP、C3P0、Proxool 优点于一身

23.7.1 C3P0

写在前面:C3P0 连接时跳一堆红字,不一定是报错……他就这样。

使用 C3P0 的前置工作:

  1. C3P0 jar 包下载

  2. ……和前面一样,把 jar 包装载好。

    为了正常运行程序,这里(上面连接下载的) jar 包(似乎)至少是:

    mchange-commons-java-0.2.19.jar

    c3p0-0.9.5.5.jar

  • 方式一:在程序中指定相关参数

    	/* [1] */
    ComboPooledDataSource cpds = new ComboPooledDataSource();
    	/* [2] */
    cpds.setDriverClass(dirver);
    cpds.setUser(user);
    cpds.setPassword(password);
    cpds.serJdbcUrl(url);
    	/* [3] */
    cpds.setInitialPoolSize(10);
    	/* [4] */
    cpds.setMaxPoolSize(50);
    	/* [5] */
    Connection connection = cpds.getConnection();
    ...
    connection.close();
    1. 创建数据源对象

      ComboPooledDataSource cpds = new ComboPooledDataSource();
    2. 设置参数

      cpds.setDriverClass(String dirver);
      cpds.setUser(String user);
      cpds.setPassword(String password);
      cpds.serJdbcUrl(String url);

      ……上面输入的形参 user 是字符串 "root"。其他同理。

    3. 设置初始化连接数

      cpds.setInitialPoolSize(10);

      初始化连接数,就是指连接池创建时,初始持有的连接数

    4. 设置最大连接数

      cpds.setMaxPoolSize(50);
    5. 获取一个连接

      Connection connection = cpds.getConnection();
  • 方式二:使用配置文件模板完成

    	/* [0] 前置操作 */ 
    	/* [1] 获取数据源对象 */ 
    ComboPooledDataSource cpds = new ComboPooledDataSource("Heruin");
    	/* [2] 获取连接 */ 
    Connection connection = cpds.getConnection();
    ...
    connection.close();
    1. 加入配置文件

      **c3p0-config.xml:**配置文件,文件名固定,放在 src/c3p0-config.xml 这个位置

      <c3p0-config>
              <!-- 数据源(连接池)名称 -->
          <named-config name="Heruin">
                  <!-- 驱动类 -->
              <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
                  <!-- url -->
              <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
                  <!-- 用户名 -->
              <property name="user">root</property>
                  <!-- 密码 -->
              <property name="password">******</property>
                  <!-- 初始化连接数 -->
              <property name="initialPoolSize">10</property>
                  <!-- 最大空闲时间 -->
              <property name="maxIdleTime">30</property>
                  <!-- 最大连接数 -->
              <property name="maxPoolSize">100</property>
                  <!-- 最小连接数 -->
              <property name="minPoolSize">10</property>
                  <!-- 每次增长的连接数 -->
              <property name="acquireIncrement">10</property>
                  <!-- 可连接的最多的命令对象数 -->
              <property name="maxStatements">10</property>
                  <!-- 每个连接可连接的最多的命令对象数 -->
              <property name="maxStatementsPerConnection">5</property>
          </named-config>
      </c3p0-config>
    2. 获取数据源对象

      ComboPooledDataSource cpds = new ComboPooledDataSource(String name);

      这里的 name 就是配置文件中的 数据源名称

    3. 获取连接

23.7.2 Druid(德鲁伊)连接池

~有的人因为看见而相信,有的人因为相信而看见!~

使用 Druid 的前置工作:

  1. Druid jar 包下载

    ……这个链接打不开的话,试试 这个

  2. 配置 jar 包

  3. 加入配置文件

    **durid.properties:**配置文件,文件名任意,放在 src/druid.properties

    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
    username=root
    password=720mel\im
    initialSize=10
    minIdle=5
    maxActive=20
    maxWait=5000
  • 使用 Druid:

    	/* [1] 加载配置文件 */
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\Druid.properties"));
    	/* [2] 初始化数据池 */
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    	/* [3] 获取连接 */
    Connection connection = dataSource.getConnection();
    ...
    connection.close();
    1. 初始化数据池

      DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    2. ……没什么好说的,但要说一下这个:

      connection.close();

      Connection 是一个接口,对于方法 close(),不同供应商有不同实现方法。

      原生的 MySQL 的实现方法是关闭连接,而这些连接池的实现方法是取消引用(放回连接池)。

23.8 Apache - DbUtils

commons-DbUtils:是 Apache 组织提供的一个开源的 JDBC 工具库。它是对 JDBC 的封装。使用 dbutils 能极大简化 JDBC 编码的工作量

DbUtils 的常用类:

  • QueryRunner 类:封装了 SQL 的执行。是线程安全的,可以实现增、删、改、查、批处理操作
  • ResultSetHandler 接口:用于处理 ResultSet,按照需求将数据转化为其他形式

JDBC 传统方法的不足:

  1. 结果集和连接是关联的。在连接关闭后,就不能使用结果集。
  2. 结果集只能使用一次,这样不便于数据管理
  3. 使用返回信息不方便

解决方案:

  • 土方法:创建一个类,其属性与表格的列一一对应。将数据遍历并读取到一个个类对象中,再将这些类对象放置到集合中。这样,就得到了一个与表格数据关联的数据集合。

    数据类,根据需要创建:

    public class SQL_Data {
        private int id;
        private Date date;
        private String name;        
    
        /* 一定要有无参构造器。这是因为后面会用到反射 */
        public SQL_Data(){}
    	
        public SQL_Data(int id, Date date, String name) {
            this.id = id;
            this.date = date;
            this.name = name;
        }   
        
        /* Geter & Seter 笔记里就省略不记了。光占地方又没技术含量 */
        ...
    }

    ……然后遍历 ResultSet,存放。就不写了。

  • 使用 DbUtils

    	/* [0] 前置操作 */
    String sql = "select * from account where ? = ?";
    	/* [1] 创建 QueryRunner */
    QueryRunner qr = new QueryRunner();
    	/* [2] 执行相关方法 */
    List<SQL_Data> list = 
    qr.query(connection, sql, new BeanListHandler<>(SQL_Data.class), "id", 1);
    ...
    	/* [3] 释放资源*/
    connection.close();
    1. 前置操作:

      获取 jar 包,并完成配置

      得到连接 Connection connection,创建数据类(这里是 SQL_Data

    2. 执行相关方法,返回结果集

      List<SQL_Data> list = 
          queryRunner.query(connection, sql, new BeanListHandler<>(SQL_Data.class), ...);
      • queryRunner.query 方法:执行 SQL 语句,把得到的 ResultSet 封装到 List 集合。这个方法会自动关闭获得的 ResultSet,所以不会造成资源泄漏

      • connection:前置操作中得到的连接

      • sql:SQL 语句

      • new BeanListHander<>(SQL_Data.class):利用反射机制,将数据封装到 SQL_Data 对象中

      • "id", 1:给 SQL 语句中的 ? 赋值。因为是可变参数,可以写多个,如:queryRunner.query(connection, sql, new BeanListHandler<>(SQL_Data.class), "id", 1, "name", "识之律者");

        ……这里,可变参数也包含列名的场合,筛选似乎会失效。还不知道原理。特此记录。

    3. 此时只需要关闭 Connection

      query() 方法已经关闭了 ResultSetPreparedStatement

23.8.1 Apache-DbUtils 常用方法

  • queryRunner.update(connection, sql, ...):DML 语句。增删改。

    后面传入可变参数,用于给 SQL 语句中的 ? 赋值

    返回值 int 代表被影响的行数。

  • queryRunner.query(connection, sql, ResultSetHandler, ...):查询。

    后面传入可变参数,用于给 SQL 语句中的 ? 赋值

    关于 ResultSetHandler

    • 传入 new BeanListHander<>(SQL_Data.class):返回多行多列。这个场合,用 List 接收返回值。
    • 传入 new BeanHandler<>(SQL_Data.class):返回单行。这个场合,用 SQL_Data data 接收返回值。
    • 传入 new ScalarHander<>():返回单行单列。这个场合,用 Object 接收返回值。

23.8.2 表和 JavaBean 的对应关系

表类型 JavaBean类型
int、samllint…… Integer
char、varchar…… String
double Double
date java.util.Date

23.9 BasicDAO

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

  1. SQL 语句固定,不能通过传入参数控制,通用性不好。
  2. select 操作的返回类型未知,需要使用泛型。
  3. 将来的表很多,业务需求复杂,不可能只靠一个 Java 类完成

DAO:数据访问对象(data access object)

  1. 我们把通用的和数据库交互的操作封装到一个通用类中,称为 BasicDAO。

  2. 在 BasicDAO 基础上,每张表对应一个特化的 DAO(继承 BasicDAO),从而更好地完成功能。

    比如:Data 表 -> Data.java 类(JavaBean)-> DataDAO.java

BasicDAO.java(示例)

public class BasicDAO<T> {
    private QueryRunner qr = new QueryRunner();

    public int update(String sql, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.update(connection, sql, parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(connection, null, null);
        }
    }

    public List<T> queryMulti(String sql, Class<T> tClass, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new BeanListHandler<T>(tClass), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(connection, null, null);
        }
    }

    public T querySingle(String sql, Class<T> tClass, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new BeanHandler<T>(tClass), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(connection, null, null);
        }
    }

    public Object queryScalar(String sql, Object... parameters){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return qr.query(connection, sql, new ScalarHandler<>(), parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtils.close(connection, null, null);
        }
    }
}

<Java>23 JDBC 和数据库连接池
https://i-melody.github.io/2022/02/20/Java/入门阶段/23 JDBC 和数据库连接池/
作者
Melody
发布于
2022年2月20日
许可协议