找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2655

积分

0

好友

375

主题
发表于 昨天 23:07 | 查看: 0| 回复: 0

Java开发中,与数据库交互是不可避免的核心环节——无论是构建管理系统、开发后端接口还是进行数据分析,都需要程序能够读写数据库中的数据。而JDBC正是连接Java与数据库的官方“桥梁”,是一套标准化的操作数据库API。许多初学者觉得JDBC复杂,有一堆接口和异常需要处理,但只要理清其核心逻辑,就会发现其模式相当固定。本文将从基础到进阶,系统地讲解JDBC,每个步骤都配有以MySQL 8.0为实例的实操代码,确保你学完即可上手实践。

一、理解基石:什么是JDBC及其价值

1、JDBC的本质:Java连接数据库的“通用协议”

JDBC,全称为Java Database Connectivity,是Java官方定义的一套接口和类,位于 java.sqljavax.sql 包下。它的核心作用在于“统一标准”——不同数据库(如MySQL、Oracle、PostgreSQL)的底层实现和语法细节存在差异。如果没有JDBC,操作不同的数据库就需要编写不同的代码,极其繁琐。

有了JDBC,情况就大为不同:Java程序只需调用JDBC的标准接口,具体的数据库通信、SQL执行等底层操作,则交由各数据库厂商提供的“驱动包”去实现。这相当于JDBC为所有数据库制定了一套“通用语言”,开发者学会这一套,就能以相似的思路操作任何数据库,仅需更换对应的驱动包和少量配置即可。

2、为何选择JDBC而非图形化工具?

有人可能会问:使用Navicat等图形化工具手动就能进行增删改查,为何还要编写代码使用JDBC?答案关键在于自动化。Navicat是“人工操作”,而JDBC实现的是“程序自动化操作”。例如,在一个用户注册功能中,用户在网页提交信息后,后台Java程序需要自动将数据存入数据库,此时显然不可能依赖人工手动插入。JDBC正是实现此类“程序自动化操作数据库”场景的核心技术。

二、JDBC核心组件:五大接口与类详解

JDBC的核心围绕5个关键接口和1个管理类展开。我们无需深究其底层实现,只需明确每个组件的作用和常用方法即可。

1. Driver(驱动接口)

这是数据库驱动的核心接口,负责与数据库建立物理连接。开发者无需自己实现,数据库厂商已经提供了实现类。例如,MySQL的驱动包中就包含了实现此接口的 com.mysql.cj.jdbc.Driver 类。我们需要做的只是将对应的驱动包导入项目。

2. DriverManager(驱动管理类)

这是一个工具类,负责管理所有已注册的驱动,并帮助我们创建数据库连接。它就像一个“中介”:你告知它数据库地址、账号和密码,它便找到对应的驱动,建立连接并将连接对象返回。常用方法有两个:

  • registerDriver(Driver driver):注册驱动(现代驱动通常自动注册,很少需要手动调用)。
  • getConnection(String url, String user, String password):核心方法,用于获取数据库连接对象。

3. Connection(连接接口)

此接口代表Java程序与数据库之间的一条活动“会话”通道。只有获得此连接对象,才能执行后续的SQL操作。常用方法包括:

  • createStatement():创建Statement对象,用于执行静态SQL语句。
  • prepareStatement(String sql):创建PreparedStatement对象,用于执行预编译SQL(推荐使用,可防SQL注入)。
  • setAutoCommit(boolean autoCommit):设置事务是否自动提交(默认为true,手动控制事务时需设为false)。
  • commit():提交事务。
  • rollback():回滚事务。
  • close():关闭连接(至关重要,必须在使用后关闭以释放数据库资源)。

4. Statement / PreparedStatement(SQL执行器)

这两个接口都用于执行SQL语句,其中PreparedStatement是Statement的子接口,功能更强,强烈推荐优先使用。

(1)Statement(普通执行器)

用于执行静态SQL语句(如固定的查询或删除)。常用方法:

  • executeQuery(String sql):执行查询语句(SELECT),返回ResultSet结果集。
  • executeUpdate(String sql):执行更新语句(INSERT、UPDATE、DELETE),返回受影响的行数。
  • close():关闭执行器。
    缺点:存在SQL注入风险。例如在用户登录场景,若使用Statement拼接用户输入的账号密码,恶意用户可能通过输入特殊字符绕过验证,安全性较差。
(2)PreparedStatement(预编译执行器)

核心优势在于预编译、防止SQL注入、提升性能(相同结构的SQL只需编译一次)。它使用 ? 作为占位符,通过 setXxx(int index, Xxx value) 方法为占位符赋值(索引从1开始)。例如:setString(1, "zhangsan")
结论:在开发中应一律使用PreparedStatement,避免使用Statement。

5. ResultSet(结果集接口)

执行查询语句(SELECT)后,返回的数据被封装在ResultSet对象中,它就像一个“虚拟表格”。我们需要通过其方法遍历数据行并获取每列的值。常用方法:

  • next():将光标移动到下一行,返回boolean值(true表示有数据,false表示已无数据)。
  • getXxx(String columnName):根据列名获取对应类型的值,如 getString("username")getInt("id")
  • getXxx(int columnIndex):根据列索引获取值(索引从1开始)。
  • close():关闭结果集。

三、JDBC完整实战:基于MySQL 8.0实现CRUD

理论结合实践,下面我们以MySQL 8.0为例,从环境准备到编码,一步步实现完整的增删改查操作。

1、环境准备

(1)启动MySQL 8.0并创建测试表

确保MySQL服务正在运行,并创建用于测试的数据库和表。例如,创建一个 test_db 数据库和 t_user 表:

CREATE DATABASE IF NOT EXISTS test_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE test_db;
drop table IF EXISTS t_user;
CREATE TABLE IF NOT EXISTS t_user (
  `id` INT NOT NULL AUTO_INCREMENT, -- 自增
  `username` VARCHAR(255) NOT NULL, -- 用户名
  `password` VARCHAR(255) NOT NULL, -- 密码
  `age` INT, -- 年龄
  `balance` decimal(20,4) DEFAULT '0.0000', -- 余额 
  PRIMARY KEY(`id`) -- 主键
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户信息表';
(2)导入MySQL驱动包

推荐使用Maven管理依赖。在项目的 pom.xml 文件中添加以下依赖(版本号请与你的MySQL版本对应):

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>  <!--请与自己的MySQL版本适配-->
    </dependency>
</dependencies>

对于不使用Maven的项目,可前往MySQL官网下载对应版本的Connector/J驱动包,并手动将其JAR文件添加到项目的类路径中。

(3)MySQL 8.0连接参数

MySQL 8.0的连接URL格式与5.x版本有所不同,需特别注意:

  • URL格式
    jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
  • 参数说明
    • localhost:3306: 数据库地址和默认端口。
    • test_db: 要连接的数据库名。
    • serverTimezone=UTC: 设置时区,解决时区不一致导致的报错(必须添加)。
    • useSSL=false: 本地测试时可禁用SSL连接,避免握手失败。
    • allowPublicKeyRetrieval=true: 解决MySQL 8.0的权限验证问题。

2、编写JDBC工具类

将获取连接和关闭资源等重复性操作封装成工具类,能极大提高代码复用率和可维护性。

import java.sql.*;

/**
 * JDBC工具类:封装获取连接、关闭资源的方法
 */
public class JDBCUtils {
    // 数据库连接参数(请根据实际情况修改)
    private static final String URL = "jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true";
    private static final String USER = "root";
    private static final String PASSWORD = "123456"; // 替换成你自己的MySQL密码

    // 静态代码块:加载驱动(MySQL 8.0+)
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL 8.0的驱动类名
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("驱动加载失败!");
        }
    }

    // 获取数据库连接
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("获取连接失败!");
        }
    }

    // 关闭资源(查询操作需要关闭ResultSet、PreparedStatement、Connection)
    public static void close(ResultSet rs, PreparedStatement pstmt, Connection conn) {
        // 关闭顺序:ResultSet → PreparedStatement → Connection(先开后关)
        try {
            if (rs != null) rs.close();
            if (pstmt != null) pstmt.close();
            if (conn != null) conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 重载关闭方法(增删改操作不需要ResultSet)
    public static void close(PreparedStatement pstmt, Connection conn) {
        close(null, pstmt, conn);
    }
}

注意:静态代码块在类加载时执行一次,用于驱动加载。

3、实现增删改查(CRUD)

核心流程统一为:获取连接 → 创建PreparedStatement → 设置参数并执行SQL → 处理结果 → 关闭资源。

(1)新增数据(INSERT)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCDemo {
    public static void main(String[] args) {
        insertUser("zhangsan", "123456", 20);
    }

    // 新增用户
    public static void insertUser(String username, String password, int age) {
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 获取连接
            conn = JDBCUtils.getConnection();
            // 2. 编写SQL模板,用?占位符
            String sql = "INSERT INTO t_user (username, password, age) VALUES (?, ?, ?)";
            // 3. 创建PreparedStatement
            pstmt = conn.prepareStatement(sql);
            // 4. 给占位符赋值(index从1开始)
            pstmt.setString(1, username);
            pstmt.setString(2, password);
            pstmt.setInt(3, age);
            // 5. 执行SQL(增删改用executeUpdate,返回受影响行数)
            int rows = pstmt.executeUpdate();
            // 6. 处理结果
            System.out.println("新增成功,受影响行数:" + rows);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭资源
            JDBCUtils.close(pstmt, conn);
        }
    }
}

执行后,可在数据库工具中查看 t_user 表,确认数据已成功插入。

(2)修改数据(UPDATE)
// 修改用户年龄
public static void updateUserAge(int id, int newAge) {
    Connection conn = null;
    PreparedStatement pstmt = null;

    try {
        conn = JDBCUtils.getConnection();
        String sql = "UPDATE t_user SET age = ? WHERE id = ?";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, newAge);  // 第一个占位符:新年龄
        pstmt.setInt(2, id);      // 第二个占位符:用户ID
        int rows = pstmt.executeUpdate();
        System.out.println("修改成功,受影响行数:" + rows);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.close(pstmt, conn);
    }
}

调用 updateUserAge(1, 25),即可将id为1的用户的年龄更新为25。

(3)删除数据(DELETE)
// 删除用户
public static void deleteUser(int id) {
    Connection conn = null;
    PreparedStatement pstmt = null;

    try {
        conn = JDBCUtils.getConnection();
        String sql = "DELETE FROM t_user WHERE id = ?";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, id);
        int rows = pstmt.executeUpdate();
        System.out.println("删除成功,受影响行数:" + rows);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.close(pstmt, conn);
    }
}

调用 deleteUser(1),即可删除id为1的用户记录。

(4)查询数据(SELECT)

查询操作需要处理ResultSet结果集,遍历并取出数据。

// 根据ID查询单个用户
public static void selectUserById(int id) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
        conn = JDBCUtils.getConnection();
        String sql = "SELECT id, username, password, age FROM t_user WHERE id = ?";
        pstmt = conn.prepareStatement(sql);
        pstmt.setInt(1, id);
        // 执行查询,返回ResultSet
        rs = pstmt.executeQuery();
        // 遍历结果集
        if (rs.next()) { // 移动到下一行,有数据则进入
            int userId = rs.getInt("id");
            String username = rs.getString("username");
            String password = rs.getString("password");
            int age = rs.getInt("age");
            System.out.println("查询到用户:");
            System.out.println("ID:" + userId + ",用户名:" + username + ",年龄:" + age);
        } else {
            System.out.println("没有查询到该用户");
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源(包含ResultSet)
        JDBCUtils.close(rs, pstmt, conn);
    }
}

若要查询所有用户,可将SQL改为 SELECT * FROM t_user,并使用 while 循环遍历ResultSet。

public static void selectAllUsers() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    // 假设已定义User实体类
    List<User> userList = new ArrayList<>();

    try {
        conn = JDBCUtils.getConnection();
        String sql = "SELECT id, username, password, age FROM t_user";
        pstmt = conn.prepareStatement(sql);
        // 执行查询,返回ResultSet
        rs = pstmt.executeQuery();
        // 遍历结果集
        while (rs.next()) { // 移动到下一行,有数据则进入
            int userId = rs.getInt("id");
            String username = rs.getString("username");
            String password = rs.getString("password");
            int age = rs.getInt("age");
            userList.add(new User(userId, username, password, age));
        }
        System.out.println("用户列表:" + userList.toString());
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        // 关闭资源(包含ResultSet)
        JDBCUtils.close(rs, pstmt, conn);
    }
}

通过以上步骤,你已经掌握了使用JDBC进行数据库操作的核心流程与最佳实践。理解并熟练运用这些组件和模式,是构建健壮Java后端应用的基础。如果在实践中遇到更复杂的需求,如事务管理、连接池优化等,可以前往云栈社区的技术论坛与更多开发者交流探讨。




上一篇:深入解析Java延迟初始化:JEP 526惰性常量如何实现性能提升与代码简化(Java 26预览特性)
下一篇:微软CEO警告AI泡沫风险:警惕资本堆砌,重构知识型工作是关键
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-25 19:23 , Processed in 0.317501 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表