Java JDBC教程

Java JDBC教程 day01

学习思路:

  1. 从宏观到微观:先理解JDBC是什么,再学习核心组件,然后掌握具体操作步骤。
  2. 理论与实践结合:每个知识点都配有清晰的解释和可直接运行的代码示例,方便跟着敲写。
  3. 问题导向:针对学习中常见的难点和企业开发中的重点(如SQL注入、事务、性能)进行深入讲解。
  4. 结构化记忆:通过清晰的章节和要点总结,帮助我们构建知识体系,便于记忆。

第一部分:JDBC基础认知 (打好地基)

在学习任何技术之前,首先要明白它是什么(What),为什么要有它(Why)。这有助于我们从根本上理解它的设计思想。JDBC的本质是一个标准,一个规范

核心概念:

  • JDBC是什么?

    • 全称:Java Database Connectivity (Java数据库连接)。
    • 本质:是Java官方定义的一套API规范(一组接口),用于让Java应用程序与各种不同的数据库进行交互。
    • 重要比喻:想象一下,现在有一个需求,需要给不同国家的人(各种数据库,如MySQL, Oracle, SQL Server)打电话。JDBC就像是提供了一个标准的电话机(Java API),我们只需要学会用这个电话机就行。具体要打给哪个国家的人,就需要安装对应国家的电话线路(数据库驱动)。
  • 为什么需要JDBC?

    • 统一接口,解耦合:如果没有JDBC,Java代码就需要针对每一种数据库(MySQL, Oracle…)写一套完全不同的连接和操作代码。这会导致代码难以维护和移植。JDBC提供了一套统一的接口,你的Java代码只需要面向JDBC接口编程,而不需要关心底层用的是什么数据库。
    • 厂商实现:Java定义了标准,具体的数据库厂商(如MySQL公司)则根据这个标准提供具体的实现类,这个实现类被称为数据库驱动(Driver)

总结: JDBC是标准,驱动是实现。我们写代码是调用JDBC标准接口,实际运行的是具体数据库的驱动。


第二部分:JDBC核心接口与类 (认识工具)

要使用JDBC,就必须了解它的核心“零件”。这部分是整个JDBC知识体系的基石,我会列出最重要的几个组件,并说明它们各自的职责。

核心组件清单:

  1. java.sql.DriverManager驱动管理器

    • 作用:管理数据库驱动。最核心的功能是根据指定的数据库URL,从已注册的驱动中找到合适的驱动,并建立一个到数据库的连接。
    • 核心方法static Connection getConnection(String url, String user, String password)
  2. java.sql.Connection数据库连接

    • 作用:代表一个与数据库的物理连接会话。所有与数据库的交互都是通过Connection对象完成的。
    • 重要比喻:这是你和数据库之间建立的“通话线路”,后续所有的数据请求和响应都通过这条线路。
    • 核心方法
      • Statement createStatement(): 创建一个用于执行静态SQL语句的Statement对象。
      • PreparedStatement prepareStatement(String sql): 创建一个用于执行预编译SQL语句的PreparedStatement对象(更常用,更安全)。
      • void close(): 关闭连接,释放资源。
      • void setAutoCommit(boolean autoCommit): 设置是否自动提交事务。
      • void commit(): 提交事务。
      • void rollback(): 回滚事务。
  3. java.sql.StatementSQL执行器 (基础版)

    • 作用:用于执行静态的、不带参数的SQL语句,并将结果返回。
    • 缺点:存在SQL注入风险,性能也相对较差。在实际开发中很少直接使用。
    • 核心方法
      • ResultSet executeQuery(String sql): 执行查询语句 (SELECT)。
      • int executeUpdate(String sql): 执行增、删、改语句 (INSERT, DELETE, UPDATE),返回受影响的行数。
      • boolean execute(String sql): 可以执行任何SQL语句。
  4. java.sql.PreparedStatementSQL执行器 (预编译版)

    • 作用Statement的子接口,用于执行预编译的、可带参数的SQL语句。
    • 优点
      • 防止SQL注入:通过占位符?来传递参数,将SQL指令和数据分离,从根本上杜绝了SQL注入的风险。
      • 性能更高:SQL语句会预先编译并缓存在数据库中,当多次执行相同结构的SQL时,效率更高。
    • 核心方法
      • void setXxx(int parameterIndex, Xxx value): 为SQL语句中的占位符?设置参数。例如 setString(), setInt()
      • ResultSet executeQuery(): 执行查询。
      • int executeUpdate(): 执行增删改。
  5. java.sql.ResultSet结果集

    • 作用:封装了执行查询操作(executeQuery)后从数据库返回的数据。
    • 重要比喻:可以看作一个指向数据库查询结果表格的行指针。初始时,指针在第一行之前。
    • 核心方法
      • boolean next(): 将指针向下移动一行。如果下一行有数据,则返回true,否则返回false这是遍历结果集的标准方式
      • Xxx getXxx(int columnIndex): 根据列的索引(从1开始)获取数据。
      • Xxx getXxx(String columnLabel): 根据列的名称获取数据(推荐使用)。
      • void close(): 关闭结果集,释放资源。

第三部分:JDBC经典操作六步曲 (动手实践)

这是最重要的实践环节。我将把JDBC的所有操作流程化、标准化,形成一个固定的“套路”。

准备工作:

  1. 确保我们的Java项目中已经添加了对应数据库的JDBC驱动JAR包(例如,mysql-connector-java-8.x.x.jar)。

  2. 准备好一个数据库,并创建一张测试表,例如:

    CREATE TABLE `user` (
      `id` int NOT NULL AUTO_INCREMENT,
      `username` varchar(50) DEFAULT NULL,
      `password` varchar(50) DEFAULT NULL,
      PRIMARY KEY (`id`)
    );
    

经典六步(以MySQL为例,使用PreparedStatement):

// 这是一个完整的、可以直接运行的JDBC示例
// 请务必亲手敲一遍,体会每一步的作用

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcQuickStart {

    // 数据库连接信息 (实际开发中应放在配置文件中)
    private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASS = "your_password";

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // ==================== 步骤 1: 加载数据库驱动 ====================
            // 在JDBC 4.0之后,这步可以省略,因为DriverManager可以自动加载classpath中的驱动。
            // 但为了理解流程,我们保留它。Class.forName("com.mysql.cj.jdbc.Driver");
            System.out.println("步骤1: 驱动加载成功(可省略)");

            // ==================== 步骤 2: 获取数据库连接 ====================
            System.out.println("步骤2: 正在连接到数据库...");
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            System.out.println("数据库连接成功!");

            // ==================== 步骤 3: 创建PreparedStatement对象 ====================
            // 准备一个带占位符的SQL语句
            String sql = "SELECT id, username, password FROM user WHERE id = ?";
            System.out.println("步骤3: 准备创建PreparedStatement...");
            pstmt = conn.prepareStatement(sql);
            
            // 为占位符设置参数
            pstmt.setInt(1, 1); // 查询ID为1的用户
            System.out.println("PreparedStatement创建成功!");

            // ==================== 步骤 4: 执行SQL语句 ====================
            System.out.println("步骤4: 正在执行查询...");
            rs = pstmt.executeQuery(); // 执行查询,返回ResultSet
            System.out.println("查询执行成功!");

            // ==================== 步骤 5: 处理结果集ResultSet ====================
            System.out.println("步骤5: 正在处理结果集...");
            // 使用while循环和rs.next()遍历结果
            while (rs.next()) {
                // 通过列名获取数据,推荐!
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String password = rs.getString("password");

                System.out.println("--------------------");
                System.out.println("查询到用户ID: " + id);
                System.out.println("用户名: " + username);
                System.out.println("密码: " + password);
                System.out.println("--------------------");
            }

        } catch (SQLException e) {
            System.err.println("数据库操作出错!");
            e.printStackTrace();
        } finally {
            // ==================== 步骤 6: 关闭资源 ====================
            // 必须在finally块中关闭资源,确保无论是否发生异常都会执行。
            // 关闭顺序:先开后关,后开先关 (ResultSet -> PreparedStatement -> Connection)
            System.out.println("步骤6: 正在关闭资源...");
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (pstmt != null) {
                    pstmt.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            System.out.println("资源已全部关闭。");
        }
    }
}

第四部分:进阶知识点与最佳实践 (提升能力)

掌握了基础操作后,要成为一个合格的开发者,还必须了解如何写出更安全、更高效、更健壮的代码。这部分内容是面试和实际工作的重点。

1. SQL注入漏洞与PreparedStatement

  • 什么是SQL注入?
    当应用程序将用户输入的内容直接拼接到SQL语句中时,恶意用户可以输入一段SQL代码来篡改原始SQL的执行逻辑。

  • 危险的Statement示例

    String username = "admin";
    String password = "' or '1'='1"; // 恶意输入
    String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'";
    // 拼接后的SQL: SELECT * FROM user WHERE username = 'admin' AND password = '' or '1'='1'
    // 这个SQL恒为真,导致无需密码即可登录。
    
  • 安全的PreparedStatement方案

    String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, "admin");
    pstmt.setString(2, "' or '1'='1"); // 恶意输入
    // 数据库会将 ' or '1'='1' 作为一个完整的字符串参数处理,而不是SQL指令。
    
  • 结论永远优先使用PreparedStatement,杜绝使用Statement进行字符串拼接!

2. 资源释放的优雅方式:try-with-resources

  • 传统finally块的痛点:代码冗长,需要层层嵌套try-catch来关闭资源,容易遗漏。

  • Java 7+ 的try-with-resources
    编译器会自动为我们生成finally块并调用资源的close()方法。代码更简洁、更安全。

  • 示例

    public static void betterQuery() {
        String sql = "SELECT id, username FROM user WHERE id = ?";
        // 将需要自动关闭的资源写在try()的括号内
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setInt(1, 1);
            
            // ResultSet也应该放在try()中,因为它也需要关闭
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("username"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 无需手动编写finally和close()
    }
    
  • 结论只要你的JDK版本 >= 7,就应该始终使用 try-with-resources 来管理JDBC资源。

3. 数据库事务 (Transaction)

  • 什么是事务?
    一组必须要么全部成功,要么全部失败的数据库操作单元。典型的例子是银行转账。

  • 事务的四大特性(ACID)

    • 原子性 (Atomicity):事务是最小的执行单位,不可分割。
    • 一致性 (Consistency):事务执行前后,数据库从一个一致性状态转移到另一个一致性状态。
    • 隔离性 (Isolation):多个并发事务之间互不干扰。
    • 持久性 (Durability):事务一旦提交,其结果就是永久性的。
  • JDBC中的事务控制

    1. conn.setAutoCommit(false); // 开启事务
    2. 执行多个executeUpdate()操作…
    3. conn.commit(); // 如果所有操作成功,提交事务
    4. catch块中:conn.rollback(); // 如果任何一步发生异常,回滚事务
  • 示例:模拟转账

    public static void transferMoney() {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 1. 开启事务
            conn.setAutoCommit(false);
    
            // A账户减100
            String sql1 = "UPDATE account SET balance = balance - 100 WHERE id = 1";
            try(PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
                pstmt1.executeUpdate();
            }
    
            // 模拟发生异常
            // if (true) throw new RuntimeException("系统故障!");
    
            // B账户加100
            String sql2 = "UPDATE account SET balance = balance + 100 WHERE id = 2";
            try(PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
                pstmt2.executeUpdate();
            }
    
            // 3. 提交事务
            conn.commit();
            System.out.println("转账成功!");
    
        } catch (Exception e) {
            System.err.println("转账失败,执行回滚!");
            // 4. 回滚事务
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            // 关闭连接等资源
            if (conn != null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }
    

4. 批处理 (Batch Processing)

  • 场景:当我们需要一次性执行大量结构相同但参数不同的SQL语句时(如批量插入1000条用户数据)。

  • 优点:减少网络通信次数,大幅提升性能。

  • 步骤

    1. 创建PreparedStatement
    2. 循环调用pstmt.setXxx()设置参数。
    3. 调用pstmt.addBatch()将当前参数的SQL添加到批处理中。
    4. 循环结束后,调用pstmt.executeBatch()执行整个批处理。
  • 示例

    // ... 获取Connection ...
    String sql = "INSERT INTO user (username, password) VALUES (?, ?)";
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        for (int i = 0; i < 100; i++) {
            pstmt.setString(1, "user_" + i);
            pstmt.setString(2, "pass_" + i);
            pstmt.addBatch(); // 添加到批处理
        }
        int[] results = pstmt.executeBatch(); // 执行批处理
        System.out.println("批处理执行完毕,影响行数:" + results.length);
    }
    // ... catch and finally ...
    

5. 连接池 (Connection Pool)

  • 为什么需要连接池?
    • 数据库连接的创建和销毁是非常耗费系统资源的昂贵操作。
    • 在Web应用中,用户请求频繁,如果每次请求都新建一个连接,服务器会很快不堪重负。
  • 工作原理
    • 应用启动时,预先创建一定数量的数据库连接,并放入一个“池子”中。
    • 当需要连接时,不是新建,而是从池中“借用”一个。
    • 使用完毕后,不是关闭,而是“归还”到池中,供其他线程复用。
  • 常用连接池技术
    • HikariCP (SpringBoot 2.x 默认,强烈推荐,性能最好)
    • Druid (阿里巴巴出品,功能强大,监控完善)
    • C3P0, DBCP (较老的连接池技术)
  • 注意:在JavaWeb开发中,我们通常不会自己去实现连接池,而是直接使用这些成熟的第三方库。我们只需要学会如何配置它们即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值