一、JDBC 入门

1. JDBC 的概述

JDBC 是 Java 提供的数据库连接 API 集合,它定义了一套操作数据库的接口,各数据库厂商基于这套接口实现 “数据库驱动”,从而让 Java 程序能兼容多种数据库(如 MySQL、Oracle 等)。

简单来说,JDBC 是 “Java 程序” 和 “数据库” 之间的 **“桥梁”**:Java 程序通过 JDBC 接口调用数据库驱动,驱动再和具体数据库交互,最终将结果返回给 Java 程序。

2. JDBC 的快速入门

实现 Java 程序连接 MySQL 并执行一条查询语句,步骤如下:

步骤 1:导入数据库驱动包

下载 MySQL 的 JDBC 驱动包(如mysql-connector-java-8.0.xx.jar),并添加到项目的类路径中。

步骤 2:编写 JDBC 代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JdbcQuickStart {
    public static void main(String[] args) throws Exception {
        // 1. 注册驱动(MySQL 8.0后可省略,驱动会自动注册)
        Class.forName("com.mysql.cj.jdbc.Driver");
        
        // 2. 获取数据库连接
        String url = "jdbc:mysql://localhost:3306/shop_db?useSSL=false&serverTimezone=UTC";
        String user = "root";
        String password = "你的数据库密码";
        Connection conn = DriverManager.getConnection(url, user, password);
        
        // 3. 获取执行SQL的对象
        Statement stmt = conn.createStatement();
        
        // 4. 执行SQL,返回结果集
        String sql = "SELECT * FROM user";
        ResultSet rs = stmt.executeQuery(sql);
        
        // 5. 处理结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            System.out.println("id=" + id + ", name=" + name + ", age=" + age);
        }
        
        // 6. 释放资源(后开的先关)
        rs.close();
        stmt.close();
        conn.close();
    }
}

步骤说明

  • 注册驱动:让 JVM 加载并初始化数据库驱动类,MySQL 8.0 及以上版本可省略该步骤(驱动类会自动注册)。
  • 获取连接:通过DriverManagergetConnection方法,传入数据库 URL、用户名、密码,建立和数据库的连接。
  • 执行 SQL:通过Statement对象执行 SQL 语句,executeQuery用于查询(返回ResultSet结果集),executeUpdate用于增删改(返回受影响的行数)。
  • 释放资源:数据库连接是稀缺资源,使用后必须关闭,避免内存泄漏或连接耗尽。

二、JDBC 相关的接口和 API

JDBC 的核心功能由几个关键类和接口支撑,掌握它们是用好 JDBC 的基础。

1. DriverManager 类(驱动管理)

它是 JDBC 的驱动管理工具类,主要作用是:

  • 注册数据库驱动(registerDriver方法,但实际开发中常用Class.forName间接注册)。
  • 获取数据库连接(getConnection方法,传入 URL、用户名、密码)。

示例:获取连接的核心代码就是依赖它:

Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/shop_db", 
    "root", 
    "password"
);

2. Connection 接口(数据库连接)

它代表Java 程序和数据库的一次连接会话,是 JDBC 的核心对象之一,主要作用:

  • 创建执行 SQL 的对象(createStatementprepareStatement等)。
  • 管理事务(setAutoCommitcommitrollback等方法)。

事务管理示例

try {
    conn.setAutoCommit(false); // 开启手动提交事务
    // 执行一系列SQL操作
    stmt.executeUpdate("UPDATE account SET balance = balance - 100 WHERE id = 1");
    stmt.executeUpdate("UPDATE account SET balance = balance + 100 WHERE id = 2");
    conn.commit(); // 提交事务
} catch (Exception e) {
    conn.rollback(); // 事务回滚
    e.printStackTrace();
}

3. Statement 接口(执行 SQL 语句)

它是执行 SQL 语句的载体,有两个常用子接口:

  • Statement:直接执行 SQL,存在SQL 注入风险(后面会讲)。
  • PreparedStatement:预编译 SQL,能有效防止 SQL 注入,是实际开发的首选。

Statement 执行 SQL 示例

Statement stmt = conn.createStatement();
// 执行查询
ResultSet rs = stmt.executeQuery("SELECT * FROM user");
// 执行增删改
int rowCount = stmt.executeUpdate("DELETE FROM user WHERE id = 3");

PreparedStatement 执行 SQL 示例

String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "李四"); // 给第一个?赋值
pstmt.setInt(2, 28);        // 给第二个?赋值
int rowCount = pstmt.executeUpdate();

4. ResultSet 接口(代表结果集)

它封装了查询 SQL 的结果集,可以通过游标(类似指针)遍历每一条记录,并获取字段值。

常用方法

  • next():游标向下移动一行,返回boolean(判断是否有下一行)。
  • getXxx(字段名/索引):获取当前行的字段值(Xxx是数据类型,如getIntgetString等)。

示例

while (rs.next()) {
    int id = rs.getInt("id"); // 通过字段名获取
    String name = rs.getString(2); // 通过字段索引(从1开始)获取
    System.out.println("id=" + id + ", name=" + name);
}

5. 释放资源

JDBC 的连接、语句、结果集都是资源型对象,使用后必须关闭,否则会导致资源泄漏。释放时要遵循 “后创建的先关闭” 的原则。

示例

try {
    if (rs != null) rs.close();
    if (stmt != null) stmt.close();
    if (conn != null) conn.close();
} catch (Exception e) {
    e.printStackTrace();
}

6. JDBC 增删改查

基于 JDBC 实现对数据库的增、删、改、查操作,是业务开发的基础。

(1)插入数据(增)

String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "王五");
pstmt.setInt(2, 30);
int rowCount = pstmt.executeUpdate();
System.out.println("插入了" + rowCount + "条数据");

(2)删除数据(删)

String sql = "DELETE FROM user WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 4);
int rowCount = pstmt.executeUpdate();
System.out.println("删除了" + rowCount + "条数据");

(3)修改数据(改)

String sql = "UPDATE user SET age = ? WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 29);
pstmt.setString(2, "王五");
int rowCount = pstmt.executeUpdate();
System.out.println("修改了" + rowCount + "条数据");

(4)查询数据(查)

String sql = "SELECT * FROM user WHERE age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 25);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
    int id = rs.getInt("id");
    String name = rs.getString("name");
    int age = rs.getInt("age");
    System.out.println("id=" + id + ", name=" + name + ", age=" + age);
}

7. JDBC 工具类的编写

在实际开发中,数据库的连接信息(URL、用户名、密码)是固定的,且 “获取连接、释放资源” 的逻辑是重复的。因此,我们可以编写一个JDBC 工具类,封装这些通用操作,提升代码复用性。

工具类代码示例

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JdbcUtils {
    // 1. 定义数据库连接常量
    private static final String URL = "jdbc:mysql://localhost:3306/shop_db?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASSWORD = "你的数据库密码";
    
    // 2. 静态代码块:注册驱动(只执行一次)
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // 3. 获取数据库连接
    public static Connection getConnection() throws Exception {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    
    // 4. 释放资源
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // 重载:支持无ResultSet的情况(增删改操作)
    public static void close(Connection conn, Statement stmt) {
        close(conn, stmt, null);
    }
}

工具类使用示例

public class JdbcUtilsTest {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        try {
            // 获取连接
            conn = JdbcUtils.getConnection();
            // 执行SQL
            String sql = "SELECT * FROM user";
            pstmt = conn.prepareStatement(sql);
            ResultSet rs = pstmt.executeQuery();
            // 处理结果
            while (rs.next()) {
                System.out.println(rs.getString("name"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            JdbcUtils.close(conn, pstmt);
        }
    }
}

8. 面试题:SQL 注入漏洞问题

(1)什么是 SQL 注入?

SQL 注入是一种攻击手段:攻击者通过在输入框中插入恶意 SQL 代码,欺骗服务器执行非预期的 SQL 操作(如越权查询、删除数据等)。

(2)如何重现 SQL 注入?

假设一个 “根据用户名和密码查询用户” 的功能,用Statement实现:

String name = "admin' OR '1'='1"; // 恶意输入
String pwd = "123' OR '1'='1";   // 恶意输入
String sql = "SELECT * FROM user WHERE name = '" + name + "' AND password = '" + pwd + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

此时 SQL 会被拼接为:SELECT * FROM user WHERE name = 'admin' OR '1'='1' AND password = '123' OR '1'='1',由于'1'='1'恒为真,这条 SQL 会查询出所有用户数据,导致越权。

(3)如何防止 SQL 注入?

核心方案是使用PreparedStatement预编译 SQL,它会将用户输入的内容视为 “参数值” 而非 “SQL 代码”,从而避免注入。

String name = "admin' OR '1'='1";
String pwd = "123' OR '1'='1";
String sql = "SELECT * FROM user WHERE name = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
pstmt.setString(2, pwd);
ResultSet rs = pstmt.executeQuery();

此时,恶意输入会被当作普通字符串处理,SQL 注入就被阻止了。

 

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐