跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java JDBC 核心技术全解析

Java JDBC 核心技术,涵盖概念、核心 API(DriverManager、Connection、Statement、PreparedStatement)、基本使用步骤、SQL 注入问题及解决方案、事务管理、连接池(Druid)配置与使用,以及工具类封装方法。通过代码示例演示增删改查、批量插入、主键回显等实战场景。

ServerBase发布于 2026/3/23更新于 2026/10/2211K 浏览

一、JDBC 概述

1、概念

  • jdbc 是 (Java Database Connectivity) 单词的缩写,翻译为 java 连接数据库
  • jdbc 是 java 程序连接数据库的技术统称
  • jdbc 由 java 语言的规范 (接口) 和各个数据库厂商的实现驱动 (jar) 组成
  • jdbc 是一种典型的面向接口编程
  • jdbc 优势
    1. 只需要学习 jdbc 规范接口的方法,即可操作所有的数据库软件
    2. 项目中期切换数据库软件,只需要更换对应的数据库驱动 jar 包,不需要更改代码

2、jdbc 核心 api 和使用路线

1、jdbc 技术组成

各个数据库厂商提供的驱动 jar 包

因为各个数据库厂商的 DBMS 软件各有不同,那么内部如何通过 sql 实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

jar 包是什么?

java 程序打成的一种压缩包格式,你可以将这些 jar 包引入你的项目中,然后你可以使用这个 java 程序中类和方法以及属性了!

jdk 下 jdbc 规范接口,存储在 java.sql 和 javax.sql 包中的 api

为了项目代码的可移植性,可维护性,SUN 公司从最初就制定了 Java 程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种 DBMS 软件,Java 代码可以保持一致性。

2、涉及具体核心类和接口
  • DriverManager
    1. 将第三方数据库厂商的实现驱动 jar 注册到程序中
    2. 可以根据数据库连接信息获取 connection
  • Connection
    • 和数据库建立的连接,在连接对象上,可以多次执行数据库 curd 动作
    • 可以获取 statement 和 preparedstatement,callablestatement 对象
  • Statement | PreparedStatement | CallableStatement
    • 具体发送 SQL 语句到数据库管理软件的对象
    • 不同发送方式稍有不同! preparedstatement 使用为重点!
  • Result
    • 面向对象思维的产物(抽象成数据库的查询结果表)
    • 存储 DQL 查询数据库结果的对象
    • 需要我们进行解析,获取具体的数据库数据
3、jdbc api 使用路线
  • JDBC API 使用路线
    • 静态 SQL 路线 (没有动态值语句)
      • DriverManager
        • Connection
          • Statement
            • Result
    • 预编译 SQL 路线 (有动态值语句)
      • DriverManager
        • Connection
          • PreparedStatement
            • Result
    • 执行标准存储过 SQL 路线
      • DriverManager
        • Connection
          • CallableStatement
            • Result

二、全新 JDBC 核心 API

1、引入 mysql-jdbc 驱动 jar

驱动 jar 版本选择

mysql 版本推荐驱动版本备注
mysql 5.5.x5.0.xcom.mysql.jdbc.Driver
mysql 5.7.x5.1.xcom.mysql.jdbc.Driver
mysql 8.x8.0.x建议:8.0.25+ 省略时区设置 com.mysql.cj.jdbc.Driver

2、POM 依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>

3、jdbc 基本使用步骤分析

  1. 注册驱动
  2. 获取连接
  3. 创建发送 sql 语句对象
  4. 发送 sql 语句,并获取返回结果
  5. 结果集解析
  6. 资源关闭

4、基于 statement 实现查询

准备数据库数据

CREATE DATABASE xx_jdbc; 
USE xx_jdbc; 
CREATE TABLE t_user(
 id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
 account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
 PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
 nickname VARCHAR(20) NOT NULL COMMENT '昵称'
);
INSERT INTO t_user(account,PASSWORD,nickname) VALUES ('root','123456','技术总监'),('admin','666666','CTO');
package com.xx;
import java.sql.*;

public class JdbcBasePart {
    public static void main(String[] args) throws SQLException {
        //1.注册驱动
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //2.获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx_jdbc", "root", "mac_root");
        //3.创建小车 Statement
        Statement statement = connection.createStatement();
        //4.发送 SQL 语句
        String sql = "select id,account,password,nickname from t_user ;";
        ResultSet resultSet = statement.executeQuery(sql);
        //5.结果集解析
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String account = resultSet.getString("account");
            String password = resultSet.getString("password");
            String nickname = resultSet.getString("nickname");
            System.out.println(id + "\t" + account + "\t" + password + "\t" + nickname);
        }
        //6.关闭资源【先开后关】
        resultSet.close();
        statement.close();
        connection.close();
    }
}

5、基于 statement 方式问题

模拟登录,控制台输入账号和密码,判断是否登陆成功成功!

package com.xx;
import java.sql.*;
import java.util.Scanner;

public class JdbcStatementLoginPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.输入账号和密码
        Scanner scanner = new Scanner(System.in);
        String account = scanner.nextLine();
        String password = scanner.nextLine();
        scanner.close();
        //2.jdbc 的查询使用
        //注册一次驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
        //固定方法固定剂
        //创建 statement
        Statement statement = connection.createStatement();
        //执行 SQL 语句 [动态 SQL 语句,需要字符串拼接]
        String sql = "select * from t_user where account = '" + account + "' and password = '" + password + "';";
        /**
         * ResultSet 结果集对象 = executeQuery(DQL 语句)
         * int 响应行数 = executeUpdate(非 DQL 语句)
         */
        ResultSet resultSet = statement.executeQuery(sql);
        //进行结果集对象解析
        if (resultSet.next()) {
            //只要向下移动,就是有数据 就是登录成功!
            System.out.println("登录成功!");
        } else {
            System.out.println("登录失败!");
        }
        //关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}
admin 666666 登录成功!
admin 123456 'or 1='1 登录成功!
1、存在问题
  1. SQL 语句需要字符串拼接,比较麻烦
  2. 只能拼接字符串类型,其他的数据库类型无法处理

可能发生注入攻击

动态值充当了 SQL 语句结构,影响了原有的查询结果!

6、基于 preparedStatement 方式优化

利用 preparedStatement 解决上述案例注入攻击和 SQL 语句拼接问题!

package com.xx;
import java.sql.*;
import java.util.Scanner;

public class JdbcPreparedStatementLoginPart {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.输入账号和密码
        Scanner scanner = new Scanner(System.in);
        String account = scanner.nextLine();
        String password = scanner.nextLine();
        scanner.close();
        //2.jdbc 的查询使用
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
        //创建 preparedStatement
        //TODO 需要传入 SQL 语句结构
        //TODO 要的是 SQL 语句结构,动态值的部分使用 ? , 占位符!
        //TODO ? 不能加 '?' ? 只能替代值,不能替代关键字和容器名
        String sql = "select * from t_user where account = ? and password = ? ;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //占位符赋值
        //给占位符赋值!从左到右,从 1 开始!
        preparedStatement.setObject(2, password);
        preparedStatement.setObject(1, account);
        //这哥们内部完成 SQL 语句拼接!
        //执行 SQL 语句即可
        ResultSet resultSet = preparedStatement.executeQuery();
        //preparedStatement.executeUpdate()
        //进行结果集对象解析
        if (resultSet.next()) {
            //只要向下移动,就是有数据 就是登录成功!
            System.out.println("登录成功!");
        } else {
            System.out.println("登录失败!");
        }
        //关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

7、基于 preparedStatement 增删改查

1、新增
/**
 * 插入一条用户数据!
 * 账号:test
 * 密码:test
 * 昵称:测试
 */
@Test
public void testInsert() throws Exception {
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
    //TODO: 切记,? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名
    String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //占位符赋值
    preparedStatement.setString(1, "test");
    preparedStatement.setString(2, "test");
    preparedStatement.setString(3, "测试");
    //发送 SQL 语句
    int rows = preparedStatement.executeUpdate();
    //输出结果
    System.out.println(rows);
    //关闭资源 close
    preparedStatement.close();
    connection.close();
}
2、修改
/**
 * 修改一条用户数据!
 * 修改账号:test 的用户,将 nickname 改为 tomcat
 */
@Test
public void testUpdate() throws Exception {
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
    //TODO: 切记,? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名
    String sql = "update t_user set nickname = ? where account = ? ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //占位符赋值
    preparedStatement.setString(1, "tomcat");
    preparedStatement.setString(2, "test");
    //发送 SQL 语句
    int rows = preparedStatement.executeUpdate();
    //输出结果
    System.out.println(rows);
    //关闭资源 close
    preparedStatement.close();
    connection.close();
}
3、删除
/**
 * 删除一条用户数据!
 * 根据账号:test
 */
@Test
public void testDelete() throws Exception {
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
    //TODO: 切记,? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名
    String sql = "delete from t_user where account = ? ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //占位符赋值
    preparedStatement.setString(1, "test");
    //发送 SQL 语句
    int rows = preparedStatement.executeUpdate();
    //输出结果
    System.out.println(rows);
    //关闭资源 close
    preparedStatement.close();
    connection.close();
}
4、查询
/**
 * 查询全部数据!
 * 将数据存到 List<Map>中
 * map -> 对应一行数据
 * map key -> 数据库列名或者别名
 * map value -> 数据库列的值
 * 1.先创建一个 List<Map>集合
 * 2.遍历 resultSet 对象的行数据
 * 3.将每一行数据存储到一个 map 对象中!
 * 4.将对象存到 List<Map>中
 * 5.最终返回
 */
@Test
public void testQueryMap() throws Exception {
    //注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
    //TODO: 切记,? 只能代替 值!!!!! 不能代替关键字 特殊符号 容器名
    String sql = "select id,account,password,nickname from t_user ;";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //占位符赋值 本次没有占位符,省略
    //发送查询语句
    ResultSet resultSet = preparedStatement.executeQuery();
    //创建一个集合
    List<Map> mapList = new ArrayList<>();
    //获取列信息对象
    ResultSetMetaData metaData = resultSet.getMetaData();
    int columnCount = metaData.getColumnCount();
    while (resultSet.next()) {
        Map map = new HashMap();
        for (int i = 1; i <= columnCount; i++) {
            map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
        }
        mapList.add(map);
    }
    System.out.println(mapList);
    //关闭资源 close
    preparedStatement.close();
    connection.close();
    resultSet.close();
}

三、全新 JDBC 扩展提升

1、自增长主键回显实现

java 程序获取插入数据时 mysql 维护自增长维护的主键id 值,这就是主键回显作用:在多表关联插入数据时,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术:

/**
 * 返回插入的主键!
 * 主键:数据库帮助维护的自增长的整数主键!
 *
 * @throws Exception
 */
@Test
public void returnPrimaryKey() throws Exception {
    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc?user=root&password=mac_root");
    //3.编写 SQL 语句结构
    String sql = "insert into t_user (account,password,nickname) values (?,?,?);";
    //4.创建预编译的 statement,传入 SQL 语句结构
    /**
     * TODO: 第二个参数填入 1 | Statement.RETURN_GENERATED_KEYS
     * 告诉 statement 携带回数据库生成的主键!
     */
    PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    //5.占位符赋值
    statement.setObject(1, "tomcat");
    statement.setObject(2, "123456");
    statement.setObject(3, "汤姆猫");
    //6.执行 SQL 语句【注意:不需要传入 SQL 语句】DML
    int i = statement.executeUpdate();
    //7.结果集解析
    System.out.println("i = " + i);
    //一行一列的数据!里面就装主键值!
    ResultSet resultSet = statement.getGeneratedKeys();
    resultSet.next();
    int anInt = resultSet.getInt(1);
    System.out.println("pk = " + anInt);
    //8.释放资源
    statement.close();
    connection.close();
}

2、批量数据插入性能提升

  • 功能需求
    1. 批量数据插入优化
    2. 提升大量数据插入效率
  • 功能实现
/**
 * 批量细节:
 * 1.url?rewriteBatchedStatements=true
 * 2.insert 语句必须使用 values
 * 3.语句后面不能添加分号;
 * 4.语句不能直接执行,每次需要装货 addBatch() 最后 executeBatch();
 *
 * @throws Exception
 */
@Test
public void batchInsertYH() throws Exception {
    //1.注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc?rewriteBatchedStatements=true", "root", "mac_root");
    //3.编写 SQL 语句结构
    String sql = "insert into t_user (account,password,nickname) values (?,?,?)";
    //4.创建预编译的 statement,传入 SQL 语句结构
    long start = System.currentTimeMillis();
    PreparedStatement statement = connection.prepareStatement(sql);
    for (int i = 0; i < 10000; i++) {
        //5.占位符赋值
        statement.setObject(1, "ergouzi" + i);
        statement.setObject(2, "lvdandan");
        statement.setObject(3, "驴蛋蛋" + i);
        //6.装车
        statement.addBatch();
    }
    //发车!批量操作!
    statement.executeBatch();
    long end = System.currentTimeMillis();
    System.out.println("消耗时间:" + (end - start));
    //8.释放资源
    connection.close();
}

3、jdbc 中数据库事务实现

1、事务概念
// 事务概念 数据库事务就是一种 SQL 语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓 存内的多条语句执行结果统一判定!一个事务内所有语句都成功及事务成功,我们可以触发 commit 提交事务来结束事务,更新数据!一个事务内任意一条语句失败,及事务失败,我们可以触发 rollback 回滚结束事务,数据回到事务之前状态!
// 优势 允许我们在失败情况下,数据回归到业务之前的状态!
// 场景 一个业务涉及多条修改数据库语句!例如:经典的转账案例,转账业务 (加钱和减钱) 批量删除 (涉及多个删除) 批量添加 (涉及多个插入)
// 事务特性
// 1. 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
// 2. 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
// 3. 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
// 4. 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
// 事务类型
// 自动提交 : 每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚!(MySQL)
// 手动提交:手动开启事务,添加语句,手动提交或者手动回滚即可!
// sql 开启事务方式
// 针对自动提交:关闭自动提交即可,多条语句添加以后,最终手动提交或者回滚!(推荐)
// SET autocommit = off; //关闭当前连接自动事务提交方式 # 只有当前连接有效
// # 编写 SQL 语句即可 SQL SQL SQL
// #手动提交或者回滚【结束当前的事务】COMMIT / ROLLBACK ;
// 手动开启事务:开启事务代码,添加 SQL 语句,事务提交或者事务回滚!(不推荐)
// 呼应 jdbc 技术
try{
    connection.setAutoCommit(false); //关闭自动提交了
    //注意,只要当前 connection 对象,进行数据库操作,都不会自动提交事务
    //数据库动作!
    //statement - 单一的数据库动作 c u r d
    connection.commit();
}catch(Exception e){
    connection.rollback();
}
2、建表
-- 继续在 xx_jdbc 的库中创建银行表
CREATE TABLE t_bank(
 id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账号主键',
 account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
 money INT UNSIGNED COMMENT '金额,不能为负值')
;
INSERT INTO t_bank(account,money) VALUES ('ergouzi',1000),('lvdandan',1000);
3、BankDao
4、BankService
package com.xx;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class BankService {
    /**
     * 转账业务方法
     *
     * @param addAccount 加钱账号
     * @param subAccount 减钱账号
     * @param money 金额
     */
    public void transfer(String addAccount, String subAccount, int money) throws ClassNotFoundException, SQLException {
        System.out.println("addAccount = " + addAccount + ", subAccount = " + subAccount + ", money = " + money);
        //注册驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        Connection connection = DriverManager.getConnection("jdbc:mysql:///xx_jdbc", "root", "mac_root");
        int flag = 0;
        //利用 try 代码块,调用 dao
        try {
            //开启事务 (关闭事务自动提交)
            connection.setAutoCommit(false);
            BankDao bankDao = new BankDao();
            //调用加钱 和 减钱
            bankDao.addMoney(addAccount, money, connection);
            System.out.println("--------------");
            bankDao.subMoney(subAccount, money, connection);
            flag = 1;
            //不报错,提交事务
            connection.commit();
        } catch (Exception e) {
            //报错回滚事务
            connection.rollback();
            throw e;
        } finally {
            connection.close();
        }
        if (flag == 1) {
            System.out.println("转账成功!");
        } else {
            System.out.println("转账失败!");
        }
    }
}
5、BankTest
package com.xx;
import org.junit.Test;

public class BankTest {
    @Test
    public void testBank() throws Exception {
        BankService bankService = new BankService();
        bankService.transfer("ergouzi", "lvdandan", 500);
    }
}

四、Druid 连接池使用

1、POM 依赖

2、硬编码方式 (了解,不推荐)

/**
 * 创建 druid 连接池对象,使用硬编码进行核心参数设置!
 * 必须参数:账号 * 密码 * url * driverClass
 * 非必须参数:初始化个数 * 最大数量等等 不推荐设置
 */
@Test
public void druidHard() throws SQLException {
    DruidDataSource dataSource = new DruidDataSource();
    //设置四个必须参数
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUsername("root");
    dataSource.setPassword("root");
    dataSource.setUrl("jdbc:mysql:///xx_jdbc");
    //获取连接
    Connection connection = dataSource.getConnection();
    //JDBC 的步骤
    //回收连接
    connection.close();
}

3、软编码方式

  • 外部配置存放在src/druid.properties
# druid 连接池需要的配置参数,key 固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///xx_jdbc
  • druid 声明代码
/**
 * 不直接在 java 代码编写配置文件!
 * 利用工厂模式,传入配置文件对象,创建连接池!
 * @throws Exception
 */
@Test
public void druidSoft() throws Exception {
    Properties properties = new Properties();
    InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
    properties.load(ips);
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
}

4、druid 配置

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:'DataSource-' + System.identityHashCode(this)
jdbcUrl连接数据库的 url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用 ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据 url 自动识别 这一项可配可不配,如果不配置 druid 会根据 url 自动识别 dbType,然后选择相应的 driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
poolPreparedStatementsfalse是否缓存 preparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 mysql 下建议关闭。
maxOpenPreparedStatements-1要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle 下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100
validationQuery用来检测连接是否有效的 sql,要求是一个查询语句。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会其作用。
testOnBorrowtrue申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义:1)Destroy 线程会检测连接的间隔时间 2)testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明
numTestsPerEvictionRun不再使用,一个 DruidDataSource 只支持一个 EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的 sql
exceptionSorter根据 dbType 自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的 filter:stat 日志用的 filter:log4j 防御 sql 注入的 filter:wall
proxyFilters类型是 List,如果同时配置了 filters 和 proxyFilters,是组合关系,并非替换关系

五、JDBC 使用优化以及工具类封装

1、jdbc 工具类封装一

位置:src/druid.properties

# druid 连接池需要的配置参数,key 固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///xx_jdbc
package com.xx;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCToolsVersion1 {
    private static DataSource ds;
    static {//静态代码块,JDBCToolsVersion1 类初始化执行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {
        return ds.getConnection(); //这么写,不能保证同一个线程,两次 getConnection() 得到的是同一个 Connection 对象
        //如果不能保证是同一个连接对象,就无法保证事务的管理
    }
    public static void free(Connection conn) throws SQLException {
        conn.setAutoCommit(true);
        conn.close();//还给连接池
    }
}

2、jdbc 工具类封装二

如何一个线程的不同方法获取同一个连接!

ThreadLocal 的介绍: JDK 1.2 的版本中就提供 java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session 等 ThreadLocal 用于保存某个线程共享变量,原因是在 Java 中,每一个线程对象中都有一个 ThreadLocalMap<ThreadLocal, Object>,其 key 就是一个 ThreadLocal,而 Object 即为该线程的共享变量。而这个 map 是通过 ThreadLocal 的 set 和 get 方法操作的。对于同一个 static ThreadLocal,不同线程只能从中 get,set,remove 自己的变量,而不会影响其他线程的变量。 1、ThreadLocal 对象.get: 获取 ThreadLocal 中当前线程共享变量的值。 2、ThreadLocal 对象.set: 设置 ThreadLocal 中当前线程共享变量的值。 3、ThreadLocal 对象.remove: 移除 ThreadLocal 中当前线程共享变量的值。

package com.xx;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/* 这个工具类的作用就是用来给所有的 SQL 操作提供'连接',和释放连接。
 * 这里使用 ThreadLocal 的目的是为了让同一个线程,在多个地方 getConnection 得到的是同一个连接。
 * 这里使用 DataSource 的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等
 */
public class JDBCTools {
    private static DataSource ds;
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    static {//静态代码块,JDBCToolsVersion1 类初始化执行
        try {
            Properties pro = new Properties();
            pro.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {
        Connection connection = tl.get();
        if (connection == null) {//当前线程还没有拿过连接,就给它从数据库连接池拿一个
            connection = ds.getConnection();
            tl.set(connection);
        }
        return connection;
    }
    public static void free() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            tl.remove();
            connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议)
            connection.close();
        }
    }
}

3、封装 BaseDao

基本上每一个数据表都应该有一个对应的 DAO 接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些 DAO 的实现类可以抽取一个公共的父类,我们称为 BaseDao

package com.xx;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;

public abstract class BaseDao {
    /* 通用的增、删、改的方法 String sql:sql Object... args:给 sql 中的?设置的值列表,可以是 0~n */
    protected int update(String sql, Object... args) throws SQLException {
        // 创建 PreparedStatement 对象,对 sql 预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);//?的编号从 1 开始,不是从 0 开始,数组的下标是从 0 开始
            }
        }
        //执行 sql
        int len = ps.executeUpdate();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }
    /* 通用的查询多个 Javabean 对象的方法,例如:多个员工对象,多个部门对象等
     * 这里的 clazz 接收的是 T 类型的 Class 对象,
     * 如果查询员工信息,clazz 代表 Employee.class,
     * 如果查询部门信息,clazz 代表 Department.class,
     */
    protected <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
        // 创建 PreparedStatement 对象,对 sql 预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);//?的编号从 1 开始,不是从 0 开始,数组的下标是从 0 开始
            }
        }
        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();
        /* 获取结果集的元数据对象。
         * 元数据对象中有该结果集一共有几列、列名称是什么等信息
         */
        ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数
        //遍历结果集 ResultSet,把查询结果中的一条一条记录,变成一个一个 T 对象,放到 list 中。
        while (res.next()) {
            //循环一次代表有一行,代表有一个 T 对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
            //把这条记录的每一个单元格的值取出来,设置到 t 对象对应的属性中。
            for (int i = 1; i <= columnCount; i++) {
                //for 循环一次,代表取某一行的 1 个单元格的值
                Object value = res.getObject(i);
                //这个值应该是 t 对象的某个属性值
                //获取该属性对应的 Field 对象
                // String columnName = metaData.getColumnName(i);//获取第 i 列的字段名
                String columnName = metaData.getColumnLabel(i);//获取第 i 列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作 private 的属性
                field.set(t, value);
            }
            list.add(t);
        }
        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }
    protected <T> T queryBean(Class<T> clazz, String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list.get(0);
    }
}

目录

  1. 一、JDBC 概述
  2. 1、概念
  3. 2、jdbc 核心 api 和使用路线
  4. 1、jdbc 技术组成
  5. 2、涉及具体核心类和接口
  6. 3、jdbc api 使用路线
  7. 二、全新 JDBC 核心 API
  8. 1、引入 mysql-jdbc 驱动 jar
  9. 2、POM 依赖
  10. 3、jdbc 基本使用步骤分析
  11. 4、基于 statement 实现查询
  12. 5、基于 statement 方式问题
  13. 1、存在问题
  14. 6、基于 preparedStatement 方式优化
  15. 7、基于 preparedStatement 增删改查
  16. 1、新增
  17. 2、修改
  18. 3、删除
  19. 4、查询
  20. 三、全新 JDBC 扩展提升
  21. 1、自增长主键回显实现
  22. 2、批量数据插入性能提升
  23. 3、jdbc 中数据库事务实现
  24. 1、事务概念
  25. 2、建表
  26. 3、BankDao
  27. 4、BankService
  28. 5、BankTest
  29. 四、Druid 连接池使用
  30. 1、POM 依赖
  31. 2、硬编码方式 (了解,不推荐)
  32. 3、软编码方式
  33. druid 连接池需要的配置参数,key 固定命名
  34. 4、druid 配置
  35. 五、JDBC 使用优化以及工具类封装
  36. 1、jdbc 工具类封装一
  37. druid 连接池需要的配置参数,key 固定命名
  38. 2、jdbc 工具类封装二
  39. 3、封装 BaseDao

更多推荐文章

查看全部
  • Vivado 许可证获取与配置指南
  • AI 编程模型深度对比:实现高效开发的选型策略
  • Spring Boot 单元测试实战:如何测试 RESTful 接口
  • 基于 BERT 模型的文本 Tokenization 详解
  • C++ 类与对象的内在机制:构造函数与析构函数
  • Hadoop 核心组件深度剖析:HDFS、YARN 与 MapReduce
  • MySQL mysqldump 导入导出结构与数据及存储过程函数事件触发器
  • WordPress 基础配置与 Java 后端开发实战笔记
  • C++ 类和对象(二):默认成员函数深度解析
  • 千笔 AI 写作工具八大核心功能解析
  • 攻防世界 Web 题解:Lottery 与 ics-05 漏洞分析
  • 图形管线与渲染引擎的 C++ 架构设计:模块化、跨平台与资源驱动
  • Web 前端基础核心知识点梳理
  • Android 开发岗位历年高频面试题及参考答案
  • WSL2 环境下 AI 助手调用摄像头方案实践
  • PyTorch 文本引导图像生成与 Stable Diffusion 实践
  • 前端国际化实现方案
  • Spring Web MVC 核心概念与实战
  • AI 应用开发不仅是调接口:从面试看技术深度与工程实践
  • 大模型 Token 机制与上下文窗口管理实战

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online