java-JDBC

1.JDBC简介

一、关系数据库基础

  1. 数据存储需求

    • 程序数据需持久化存储,自定义格式存在读写复杂、查询低效等问题,数据库应运而生。
  2. 关系模型核心

    • 基于表结构存储数据,通过外键关联实现表间 “一对多” 关系。
    • 使用 SQL 语句查询数据,例:SELECT * FROM classes WHERE grade_id = '1'

二、NoSQL 与 SQL 对比

  • NoSQL 虽宣传速度和规模优势,但 SQL 是数据库基础,理解 SQL 是掌握 NoSQL 的前提,不可忽视。

三、数据库类别

  1. 付费商用数据库

    • 代表:Oracle、SQL Server、DB2、Sybase。
    • 特点:不开源、需付费,适合需要厂商技术支持的企业场景。
  2. 免费开源数据库

    • 推荐:

      • MySQL:普及率高、工具丰富,适合 Web 开发。
      • PostgreSQL:学术性强,功能强大但知名度较低。
      • SQLite:轻量级,适合嵌入式系统和桌面应用。

四、MySQL 安装与配置要点

  1. 版本选择

    • 下载免费的MySQL Community Server版本。
  2. 编码配置

    • Windows:安装时直接选择 UTF-8 编码。

    • Mac/Linux:修改配置文件/etc/my.cnf/etc/mysql/my.cnf,添加以下内容:

      [client]
      default-character-set = utf8
      [mysqld]
      default-storage-engine = INNODB
      character-set-server = utf8
      collation-server = utf8_general_ci
      
    • 高版本优化:若 MySQL≥5.5.3,可设置utf8mb4编码(兼容 utf8,支持 emoji 等字符)。

  3. 编码验证

    • 登录 MySQL 命令行,执行show variables like '%char%',检查结果是否包含utf8字样。

五、JDBC 核心内容

  1. 定义

    • Java Database Connectivity的缩写,是 Java 程序访问数据库的标准接口,位于java.sql包(JDK 标准库)。
  2. 架构原理

    • Java 程序通过 JDBC 接口调用数据库,具体实现由数据库厂商提供的 JDBC 驱动(如 MySQL 的 jar 包)完成。
    • 驱动功能:封装网络通讯细节,实现与数据库的底层交互。
  3. 核心优势

    • 接口统一:各数据库厂商遵循相同接口,Java 代码无需针对不同数据库单独开发。
    • 低依赖:编译期仅依赖java.sql包,运行时动态加载数据库驱动 jar 包。
    • 易迁移:更换数据库时,只需替换驱动 jar 包,核心代码改动极小。

2.JDBC查询

一、JDBC 基础概念

  1. JDBC 接口与驱动

    • JDBC 是 Java 标准库java.sql中的接口规范,用于连接数据库。

    • 数据库厂商需提供 JDBC 驱动(实现接口的 jar 包),如 MySQL 驱动mysql-connector-java

    • Maven 依赖配置

      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.47</version>
          <scope>runtime</scope> <!-- 仅运行时需要,避免编译期混淆 -->
      </dependency>
      
  2. 数据库准备

    • 通过 SQL 脚本创建数据库、表及初始数据(以 MySQL 为例),需注意 MySQL 8.x 版本的用户创建语句差异。

二、连接数据库(Connection)

  1. URL 格式

    • 示例(MySQL):

      jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8
      
      • 参数说明:hostname(主机名)、port(端口,默认 3306)、db(数据库名),useSSL关闭加密,characterEncoding指定字符编码。
  2. 获取连接

    • 使用DriverManager.getConnection(url, user, password)获取连接。

    • 资源管理最佳实践:通过try (resource)自动释放连接,避免资源泄漏。

      try (Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD)) {
          // 操作数据库
      } // 自动关闭连接
      

三、执行 JDBC 查询

  1. 查询步骤

    • 步骤 1:创建StatementPreparedStatement对象。
    • 步骤 2:执行查询语句,返回ResultSet结果集。
    • 步骤 3:遍历ResultSet读取数据。
  2. Statement vs PreparedStatement

    • Statement:直接拼接 SQL,存在SQL 注入风险(如用户输入恶意字符串篡改查询逻辑)。

    • PreparedStatement

      • 使用?作为占位符,避免 SQL 注入。

      • 示例:

        String sql = "SELECT * FROM students WHERE gender=? AND grade=?";
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setObject(1, 1); // 设置参数(索引从1开始)
            ps.setObject(2, 3);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    long id = rs.getLong("id"); // 按列名读取更易读
                    String name = rs.getString("name");
                }
            }
        }
        
      • 优势:安全、高效(可复用数据库查询缓存)。

  3. ResultSet 操作要点

    • rs.next():移动到下一行,返回boolean判断是否有数据(初始位置在无数据行,需先调用next())。

    • 列索引从1开始,可通过列名(如rs.getString("name"))或索引读取数据。

    • 聚合查询(如SUM(score))结果仍通过ResultSet读取:

      if (rs.next()) {
          double sum = rs.getDouble(1); // 读取第一列
      }
      

四、SQL 注入防护

  • 核心问题:拼接 SQL 时,用户输入的恶意字符串可能篡改查询逻辑(如" OR 1=1 --"绕过验证)。
  • 解决方案必须使用PreparedStatement,禁止拼接 SQL 字符串。

五、数据类型映射

SQL 数据类型Java 数据类型
BITBOOLboolean
INTEGERint
BIGINTlong
REALfloat
FLOATDOUBLEdouble
CHARVARCHARString
DECIMALBigDecimal
DATEjava.sql.DateLocalDate
TIMEjava.sql.TimeLocalTime
  • 注意:仅最新 JDBC 驱动支持LocalDate/LocalTime

3.JDBC更新

(一)CRUD 概念

数据库操作主要包括增(Create)、删(Delete)、改(Update)、查(Retrieve),简称 CRUD,本文重点介绍增、改、删操作(更新操作)。

(二)插入操作(INSERT)

  • 实现方式:使用 PreparedStatement 执行 INSERT 语句,通过 executeUpdate () 方法执行,返回值为插入的记录数量(通常为 1)。

  • 示例代码要点

    • 设置参数时,占位符 “?” 的索引从 1 开始,通过 setObject () 等方法设置对应参数。
    • 示例代码:
    try (Connection conn = DriverManager.getConnection(...)) {
        try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (...) VALUES (?,?,?,?)")) {
            ps.setObject(1, 999); 
            ps.setObject(2, 1); 
            ps.setObject(3, "Bob"); 
            ps.setObject(4, "M"); 
            int n = ps.executeUpdate(); // 返回1
        }
    }
    
  • 注意事项:严格避免手动拼 SQL 字符串,防止安全漏洞。

(三)插入并获取自增主键

  • 适用场景:当表设置自增主键时,插入后需获取自动生成的主键值。

  • 关键步骤

    • 创建 PreparedStatement 时,指定 Statement.RETURN_GENERATED_KEYS 标志位,告知 JDBC 驱动返回自增主键。
    • 执行 executeUpdate () 后,通过 getGeneratedKeys () 获取 ResultSet 对象,从中读取主键值。
  • 示例代码要点

    try (Connection conn = ...) {
        try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (...) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS)) {
            // 设置参数
            int n = ps.executeUpdate(); 
            try (ResultSet rs = ps.getGeneratedKeys()) {
                if (rs.next()) {
                    long id = rs.getLong(1); // 索引从1开始
                }
            }
        }
    }
    
  • 注意事项:若一次插入多条记录,ResultSet 可能有多行;自增列不一定是主键,可能有多个自增列。

(四)更新操作(UPDATE)

  • 实现方式:使用 PreparedStatement 执行 UPDATE 语句,通过 executeUpdate () 方法执行,返回值为实际更新的行数(可能为正数或 0)。

  • 示例代码要点

    try (Connection conn = ...) {
        try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
            ps.setObject(1, "Bob"); 
            ps.setObject(2, 999); 
            int n = ps.executeUpdate(); // 返回更新的行数
        }
    }
    

(五)删除操作(DELETE)

  • 实现方式:使用 PreparedStatement 执行 DELETE 语句,通过 executeUpdate () 方法执行,返回值为删除的行数。

  • 示例代码要点

    try (Connection conn = ...) {
        try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
            ps.setObject(1, 999); 
            int n = ps.executeUpdate(); // 返回删除的行数
        }
    }
    

4. JDBC事务

一、数据库事务基础

  1. 定义
    由若干 SQL 语句构成的操作序列,确保所有操作要么全部执行成功,要么全部回滚,类似于 Java 的synchronized同步机制。

  2. ACID 特性

    • 原子性(Atomicity) :事务中的操作要么全成功,要么全失败。
    • 一致性(Consistency) :事务执行前后,数据状态保持合法。
    • 隔离性(Isolation) :多个事务并发执行时,互不干扰。
    • 持久性(Durability) :事务提交后,数据变更永久保存。

二、事务隔离级别

  1. SQL 标准定义的 4 种隔离级别

    隔离级别脏读(Dirty Read)不可重复读(Non Repeatable Read)幻读(Phantom Read)
    读未提交(Read Uncommitted)
    读已提交(Read Committed)-
    可重复读(Repeatable Read)--
    串行化(Serializable)---
  2. 说明

    • 脏读:事务 A 读取到事务 B 未提交的数据,若 B 回滚,A 读取的数据无效。
    • 不可重复读:事务 A 两次读取同一数据,期间事务 B 修改并提交,导致 A 两次结果不一致。
    • 幻读:事务 A 按条件查询数据,事务 B 插入 / 删除符合条件的数据并提交,导致 A 两次查询结果集不同。
    • 默认隔离级别:MySQL 为REPEATABLE_READ,其他数据库可能不同。

三、JDBC 事务实现

  1. 关键步骤

    Connection conn = openConnection();
    try {
        conn.setAutoCommit(false); // 关闭自动提交,开启事务
        // 执行多条SQL语句(如insert、update、delete)
        conn.commit(); // 提交事务
    } catch (SQLException e) {
        conn.rollback(); // 捕获异常并回滚事务
    } finally {
        conn.setAutoCommit(true); // 恢复自动提交模式
        conn.close(); // 关闭连接
    }
    
  2. 核心方法

    • setAutoCommit(false):关闭自动提交,开始事务。
    • commit():提交事务,所有操作生效。
    • rollback():回滚事务,撤销所有未提交操作。
    • 注意:默认情况下Connection处于自动提交模式(每条 SQL 单独作为事务执行),需显式关闭以开启批量事务。
  3. 设置隔离级别

    conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 设置为读已提交级别
    
    • 若未显式设置,使用数据库默认隔离级别。

四、示例与实践

  • 场景:转账操作需保证原子性,两条 SQL 必须在同一事务中执行:

    UPDATE accounts SET balance = balance - 100 WHERE id = 123 AND balance >= 100;
    UPDATE accounts SET balance = balance + 100 WHERE id = 456;
    
    • 若第一条成功、第二条失败,事务回滚,避免资金丢失。

5.JDBC批处理

为什么需要 JDBC 批处理?

在 Java 开发中,当需要执行大量相同 SQL 语句(如批量插入、更新)时,循环单条执行会导致频繁的数据库交互,性能低下
JDBC 批处理(Batch Processing)通过将多条参数不同的 SQL 合并为一个批次执行,减少网络请求次数和数据库解析开销,大幅提升性能。
场景举例

  • 批量插入用户积分记录
  • 批量更新商品库存状态
  • 批量导入 Excel 数据到数据库

核心实现:三步完成批处理

1. 创建预编译语句(PreparedStatement)

使用带占位符的 SQL 语句,避免重复解析 SQL:

String sql = "INSERT INTO students (name, gender, score) VALUES (?, ?, ?)";
PreparedStatement ps = conn.prepareStatement(sql); // conn为数据库连接
2. 循环添加参数到批次

通过addBatch()方法将不同参数组加入批次,复用同一 PreparedStatement 对象

for (Student student : studentList) {
    ps.setString(1, student.getName());
    ps.setBoolean(2, student.isGender());
    ps.setInt(3, student.getScore());
    ps.addBatch(); // 添加到批次
}
3. 执行批次并处理结果

调用executeBatch()一次性执行所有语句,返回值为每个语句影响的行数数组:

int[] updateCounts = ps.executeBatch(); // 执行批次
for (int count : updateCounts) {
    System.out.println("影响行数:" + count); // 输出每条语句的执行结果
}

关键优势对比:批处理 vs 单条执行

维度单条执行批处理执行
数据库交互次数每次执行 1 次网络请求整个批次仅 1 次网络请求
性能低(尤其数据量大时)高(性能提升 5-10 倍)
代码复杂度简单,但循环次数多需要维护批次逻辑

6.JDBC连接池

一、核心概念

  1. 作用

    • 避免频繁创建和销毁 JDBC 连接带来的开销,通过复用连接提高数据库操作效率。
    • 类比线程池,减少资源消耗,提升系统性能。
  2. 标准接口

    • 基于javax.sql.DataSource接口实现,该接口位于 Java 标准库,仅定义规范,需结合具体实现类使用。
  3. 常用实现

    • HikariCP(当前最流行)、C3P0、BoneCP、Druid。

二、HikariCP 使用示例

  1. 添加依赖

    • Maven 依赖:com.zaxxer:HikariCP:2.7.1(或其他版本)。
  2. 创建连接池

    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); // 数据库URL
    config.setUsername("root"); // 用户名
    config.setPassword("password"); // 密码
    // 配置参数
    config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
    config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
    config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
    DataSource ds = new HikariDataSource(config); // 创建连接池实例
    
  3. 获取与释放连接

    try (Connection conn = ds.getConnection()) { // 从连接池获取连接
        // 执行SQL操作
    } // 自动释放连接回连接池(非真正关闭)
    

三、关键要点

  1. 全局变量存储

    • DataSource创建成本高,需作为全局变量贯穿应用生命周期,示例:

      • 使用静态变量:static Map<String, Object> global = new HashMap<>(); global.put("dataSource", ds);
      • Web 场景可绑定到ServletContext
  2. 连接复用机制

    • 首次获取连接时创建,调用conn.close()时释放回池,标记为 “空闲” 而非销毁。
  3. 配置参数

    • 核心参数:最小 / 最大连接数、空闲超时时间、连接超时时间等,需根据负载合理配置。
    • 多数连接池支持实时状态监控。