JDBC核心教程

JDBC核心教程

一、JDBC核心概念

JDBC(Java Database Connectivity)是Java平台提供的一套标准API,用于Java程序连接和操作关系型数据库(如MySQL、Oracle),它定义了统一的接口规范,屏蔽了不同数据库的底层实现差异,让开发者无需关心具体数据库的细节,只需面向接口编程,就能实现"一次编码,多库兼容"的效果(理论上更换数据库只需替换驱动和连接信息,无需修改核心业务代码)。

JDBC的本质是"接口+实现"的设计模式:Java官方定义接口(位于java.sql和javax.sql包下),各数据库厂商(如MySQL、Oracle)提供接口的实现类,即JDBC驱动,Java程序通过调用接口方法,间接调用驱动中的实现类,完成与数据库的交互。

1.1 JDBC核心作用

  • 建立Java程序与数据库之间的连接;

  • 向数据库发送SQL语句(增删改查、事务控制等);

  • 接收数据库返回的执行结果(查询结果集、影响行数等);

  • 处理数据库交互过程中的异常,释放资源。

1.2 JDBC与ORM框架的关系

很多开发者会混淆JDBC与MyBatis、Hibernate等ORM框架,核心关系如下:

  • JDBC:底层基础API,是操作数据库的"手动工具",需要手动编写SQL、处理结果集和资源,代码繁琐但灵活性极高;

  • ORM框架:基于JDBC封装的高级工具,通过注解或XML映射数据,简化SQL编写和结果集映射,提升开发效率,但底层仍依赖JDBC。

类比:JDBC是"手动挡汽车",需手动控制细节;ORM框架是"自动挡汽车",自动处理底层逻辑,更易操作但灵活度稍低。

1.3 JDBC核心组件(5个核心接口/类)

JDBC的所有操作,都围绕以下5个核心组件展开,协同完成数据库交互:

  • DriverManager(驱动管理器):管理JDBC驱动,负责加载驱动、创建并获取数据库连接(Connection),是连接数据库的入口。

  • Connection(数据库连接):代表Java程序与数据库之间的物理连接,是所有数据库操作的基础,还负责事务控制。

  • Statement(SQL执行器):用于执行静态SQL语句,但存在SQL注入风险,生产环境不推荐使用。

  • PreparedStatement(预编译SQL执行器):继承自Statement,支持预编译SQL和参数占位符,可防止SQL注入,性能更优,是生产环境首选。

  • ResultSet(结果集):存储查询语句(SELECT)返回的结果,提供多种方法遍历和提取数据。

二、JDBC开发环境搭建

使用JDBC操作数据库,需准备3个核心组件:JDK(推荐JDK8,兼容性最好)、MySQL数据库(8.0版本)、MySQL JDBC驱动(MySQL Connector/J),搭建步骤如下。

2.1 下载并导入JDBC驱动

驱动是Java程序与MySQL交互的"桥梁",有两种导入方式,推荐使用Maven依赖(自动管理版本,无需手动下载)。

方式1:Maven依赖

在Maven项目的pom.xml文件中,添加以下依赖(版本需与MySQL版本适配:MySQL 8.0用8.0.xx版本,MySQL 5.7用5.1.xx版本):

xml 复制代码
<!-- MySQL JDBC驱动依赖 -->
<dependency>
    <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version> <!-- 版本适配MySQL 8.0 -->
</dependency>

提示:国内开发者可配置阿里云Maven镜像,加速依赖下载,在Maven安装目录的conf/settings.xml中添加镜像配置即可。

方式2:手动下载导入
  1. 从MySQL官网下载对应版本的驱动JAR包:dev.mysql.com/downloads/c...

  2. 在IDEA中创建lib目录,将下载的JAR包放入;

  3. 右键JAR包,选择"Add as Library",完成导入。

2.2 准备测试环境

提前创建测试数据库和表,后续所有示例均基于此结构:

sql 复制代码
-- 1. 创建测试数据库
CREATE DATABASE IF NOT EXISTS jdbc_demo 
CHARACTER SET utf8mb4 
COLLATE utf8mb4_general_ci;

-- 2. 切换数据库
USE jdbc_demo;

-- 3. 创建学生表
CREATE TABLE IF NOT EXISTS student (
    id INT PRIMARY KEY AUTO_INCREMENT, -- 自增主键
    name VARCHAR(50) NOT NULL, -- 姓名(非空)
    age INT CHECK (age > 0 AND age < 100), -- 年龄范围约束
    gender ENUM('男', '女', '其他') DEFAULT '男', -- 性别默认值
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP -- 创建时间默认当前时间
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 4. 插入测试数据
INSERT INTO student (name, age, gender)
VALUES ('张三', 20, '男'), ('李四', 19, '女'), ('王五', 21, '男');

2.3 定义连接常量

将数据库连接信息(URL、用户名、密码)定义为常量,方便后续维护和修改,避免硬编码导致的维护成本:

java 复制代码
public class JDBCConstants {
    // 数据库连接URL(MySQL 8.0+ 需指定时区,避免时区异常)
    public static final String URL = "jdbc:mysql://localhost:3306/jdbc_demo?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8";
    // 数据库用户名(替换为自己的MySQL用户名)
    public static final String USERNAME = "root";
    // 数据库密码(替换为自己的MySQL密码)
    public static final String PASSWORD = "123456";
}

URL参数说明:serverTimezone=Asia/Shanghai指定时区为上海,避免时区偏差;useSSL=false关闭SSL连接(开发环境可关闭);characterEncoding=utf8确保中文编码正常,杜绝乱码。

三、JDBC核心操作

JDBC操作数据库的核心流程固定:加载驱动 → 获取连接 → 创建SQL执行器 → 执行SQL → 处理结果 → 关闭资源,以下是增删改查(CRUD)的完整实战示例,均使用PreparedStatement(防SQL注入,生产标准)。

3.1 新增数据(INSERT)

向student表插入一条新数据,使用参数占位符(?)传递参数,避免SQL注入,同时确保资源正常关闭。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCInsertDemo {
    public static void main(String[] args) {
        // 声明核心组件,放在try外部,方便finally关闭
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 加载JDBC驱动(MySQL 8.0+ 支持SPI机制,可省略此步,兼容老版本建议保留)
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. 获取数据库连接
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);

            // 3. 定义SQL(使用?作为参数占位符)
            String sql = "INSERT INTO student (name, age, gender) VALUES (?, ?, ?)";

            // 4. 创建PreparedStatement对象,预编译SQL
            pstmt = conn.prepareStatement(sql);

            // 5. 设置参数(索引从1开始,与SQL中?的顺序一致)
            pstmt.setString(1, "赵六"); // 第一个?对应name字段
            pstmt.setInt(2, 22);       // 第二个?对应age字段
            pstmt.setString(3, "男");   // 第三个?对应gender字段

            // 6. 执行SQL(INSERT/UPDATE/DELETE用executeUpdate(),返回受影响的行数)
            int rows = pstmt.executeUpdate();

            // 7. 处理执行结果
            System.out.println("新增成功,受影响行数:" + rows);

        } catch (ClassNotFoundException e) {
            // 驱动加载失败异常(如依赖未导入、类名写错)
            e.printStackTrace();
        } catch (SQLException e) {
            // 数据库操作异常(如SQL语法错误、连接失败)
            e.printStackTrace();
        } finally {
            // 8. 关闭资源(顺序:ResultSet → PreparedStatement → Connection,逆序关闭,避免内存泄漏)
            try {
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.2 查询数据(SELECT)

查询student表的所有数据,使用ResultSet遍历结果集,提取每条数据的字段值,注意结果集的遍历方式。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JDBCSelectDemo {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null; // 查询操作需用到ResultSet存储结果集

        try {
            // 1. 加载驱动(可省略)
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. 获取连接
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);

            // 3. 定义查询SQL
            String sql = "SELECT id, name, age, gender, create_time FROM student";

            // 4. 创建PreparedStatement,预编译SQL
            pstmt = conn.prepareStatement(sql);

            // 5. 执行查询(SELECT用executeQuery(),返回ResultSet结果集)
            rs = pstmt.executeQuery();

            // 6. 遍历结果集(rs.next():移动到下一条数据,返回true表示有数据)
            while (rs.next()) {
                // 提取字段值(推荐用字段名,避免列索引变化导致错误)
                int id = rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String gender = rs.getString("gender");
                String createTime = rs.getString("create_time");

                // 打印结果
                System.out.printf("id:%d,姓名:%s,年龄:%d,性别:%s,创建时间:%s%n",
                        id, name, age, gender, createTime);
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源(顺序:ResultSet → PreparedStatement → Connection)
            try {
                if (rs != null) rs.close();
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3 修改数据(UPDATE)

修改指定id的学生信息,核心逻辑与新增一致,重点是通过参数占位符传递修改后的值和条件。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

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

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);

            // 定义修改SQL:修改id=4的学生年龄为23
            String sql = "UPDATE student SET age = ? WHERE id = ?";
            pstmt = conn.prepareStatement(sql);

            // 设置参数(先设置修改后的值,再设置条件参数)
            pstmt.setInt(1, 23); // 第一个?:修改后的年龄
            pstmt.setInt(2, 4);  // 第二个?:条件id

            // 执行修改,获取受影响行数
            int rows = pstmt.executeUpdate();
            System.out.println("修改成功,受影响行数:" + rows);

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4 删除数据(DELETE)

删除指定条件的学生数据,注意添加WHERE条件,避免误删全表数据,这是生产环境的重要规范。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

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

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);

            // 定义删除SQL:删除id=4的学生(必须加WHERE条件)
            String sql = "DELETE FROM student WHERE id = ?";
            pstmt = conn.prepareStatement(sql);

            // 设置条件参数
            pstmt.setInt(1, 4);

            // 执行删除
            int rows = pstmt.executeUpdate();
            System.out.println("删除成功,受影响行数:" + rows);

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

四、JDBC进阶特性

4.1 事务控制

JDBC的事务管理通过Connection对象实现,核心是控制事务的提交(commit)和回滚(rollback),确保一组SQL操作要么全部成功,要么全部失败,避免数据错乱(如转账场景)。

JDBC默认开启自动提交(每执行一条SQL自动提交),手动控制事务需关闭自动提交,执行完所有SQL后手动提交,出现异常则回滚。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JDBCTransactionDemo {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);

            // 1. 关闭自动提交,开启手动事务
            conn.setAutoCommit(false);

            // 2. 执行一组SQL操作(模拟转账:张三减100,李四加100)
            // 操作1:张三年龄减1(模拟扣款)
            String sql1 = "UPDATE student SET age = age - 1 WHERE name = ?";
            pstmt1 = conn.prepareStatement(sql1);
            pstmt1.setString(1, "张三");
            pstmt1.executeUpdate();

            // 操作2:李四年龄加1(模拟收款)
            String sql2 = "UPDATE student SET age = age + 1 WHERE name = ?";
            pstmt2 = conn.prepareStatement(sql2);
            pstmt2.setString(1, "李四");
            pstmt2.executeUpdate();

            // 3. 所有操作执行成功,手动提交事务
            conn.commit();
            System.out.println("事务提交成功,操作完成!");

        } catch (Exception e) {
            e.printStackTrace();
            try {
                // 4. 出现异常,回滚事务(撤销所有操作)
                if (conn != null) conn.rollback();
                System.out.println("事务回滚,操作失败!");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            // 关闭资源
            try {
                if (pstmt1 != null) pstmt1.close();
                if (pstmt2 != null) pstmt2.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

进阶:JDBC支持设置保存点(Savepoint),可实现事务的部分回滚,适用于复杂业务场景,即回滚到指定保存点,而不是回滚整个事务。

4.2 连接池

传统JDBC每次操作数据库都需要创建和关闭连接,连接的创建和销毁成本极高,频繁操作会导致系统性能下降,甚至耗尽数据库连接。连接池的核心作用是"复用连接",提前创建一批连接存入池中,使用时从池中获取,用完后归还,无需重复创建和销毁。

生产环境常用的连接池:Druid(阿里出品,性能最优、功能最全)、C3P0(老牌连接池,稳定性好)、HikariCP(Spring Boot默认连接池,轻量高效),以下是Druid连接池的简单使用示例。

Druid连接池使用步骤
  1. 添加Druid Maven依赖:
xml 复制代码
<!-- Druid连接池依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.16</version>
</dependency>
  1. 使用Druid获取连接,替代DriverManager:
java 复制代码
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DruidDemo {
    public static void main(String[] args) {
        // 1. 创建Druid数据源(连接池)
        DruidDataSource dataSource = new DruidDataSource();

        // 2. 配置连接池参数(与JDBC连接信息一致)
        dataSource.setUrl(JDBCConstants.URL);
        dataSource.setUsername(JDBCConstants.USERNAME);
        dataSource.setPassword(JDBCConstants.PASSWORD);

        // 3. 配置连接池核心参数(优化性能)
        dataSource.setInitialSize(5); // 初始连接数
        dataSource.setMaxActive(20); // 最大连接数
        dataSource.setMinIdle(3);    // 最小空闲连接数
        dataSource.setMaxWait(1000); // 获取连接的最大等待时间(毫秒)

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 4. 从连接池获取连接(无需手动创建,用完归还)
            conn = dataSource.getConnection();

            // 后续操作与普通JDBC一致
            String sql = "SELECT * FROM student";
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.println("姓名:" + rs.getString("name") + ",年龄:" + rs.getInt("age"));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源(连接会自动归还到连接池,无需销毁)
            try {
                if (rs != null) rs.close();
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

4.3 批处理

当需要批量执行大量SQL语句(如批量插入、批量修改)时,使用JDBC的批处理功能,可大幅提升执行效率,减少与数据库的交互次数。

java 复制代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

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

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection(JDBCConstants.URL, JDBCConstants.USERNAME, JDBCConstants.PASSWORD);
            conn.setAutoCommit(false); // 关闭自动提交,提升批处理效率

            // 批量插入SQL
            String sql = "INSERT INTO student (name, age, gender) VALUES (?, ?, ?)";
            pstmt = conn.prepareStatement(sql);

            // 批量添加参数(模拟批量插入3条数据)
            for (int i = 0; i < 3; i++) {
                pstmt.setString(1, "批量用户" + (i+1));
                pstmt.setInt(2, 20 + i);
                pstmt.setString(3, "男");
                pstmt.addBatch(); // 将SQL添加到批处理队列
            }

            // 执行批处理,返回每条SQL的受影响行数
            int[] rows = pstmt.executeBatch();
            conn.commit(); // 提交事务

            System.out.println("批量插入成功,总受影响行数:" + rows.length);

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
            try {
                if (conn != null) conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            try {
                if (pstmt != null) pstmt.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

五、JDBC常见问题与避坑指南

  • 驱动加载失败(ClassNotFoundException):检查JDBC驱动依赖是否导入、驱动类名是否正确(MySQL 8.0是com.mysql.cj.jdbc.Driver,5.7是com.mysql.jdbc.Driver)。

  • 数据库连接失败(SQLException):检查URL参数(时区、编码)是否正确、MySQL服务是否启动、用户名密码是否正确、3306端口是否被占用。

  • SQL注入风险:禁止使用Statement执行动态SQL,必须使用PreparedStatement的参数占位符(?),避免直接拼接SQL字符串。

  • 资源泄漏:必须在finally中关闭ResultSet、PreparedStatement、Connection,顺序为逆序关闭;使用连接池时,确保连接正常归还。

  • 时区异常:MySQL 8.0+ 必须在URL中指定serverTimezone(如Asia/Shanghai),否则会出现时区偏差导致连接失败。

  • 中文乱码:URL中添加characterEncoding=utf8,同时确保数据库、表的编码为utf8mb4,驱动版本与MySQL版本适配。

六、JDBC核心总结

  1. JDBC是Java操作关系型数据库的基础API,核心是"接口+驱动",屏蔽数据库底层差异,实现统一操作。

  2. 核心流程:加载驱动 → 获取连接 → 创建SQL执行器 → 执行SQL → 处理结果 → 关闭资源,固定且通用。

  3. 生产环境首选PreparedStatement(防SQL注入)、连接池(复用连接)、事务控制(保证数据安全),这三者是JDBC进阶的核心。

  4. 避坑重点:驱动版本与MySQL版本适配、资源必须关闭、避免SQL注入、正确配置URL参数。

  5. JDBC是MyBatis等ORM框架的底层基础,掌握JDBC原理,能更好地理解ORM框架的工作机制,排查底层问题。

相关推荐
京师20万禁军教头1 小时前
33面向对象(中级)-object类详解
java
一个小浪吴啊1 小时前
重构 AI 编程流:基于 Hermes 记忆中枢与 OpenCode 执行终端的 Harness 工程化实践
java·人工智能·opencode·harness·hermes
smallyoung1 小时前
RAG Chunking 全攻略:5 种策略 + LangChain4j 实战代码
人工智能·后端
小强19881 小时前
Python中的"设计模式":这5个技巧让代码优雅得像诗
后端
Cosolar1 小时前
🚀本地大模型部署指南:16G/32G/64GB内存配置全解析(附最新模型速查表)
人工智能·后端·llm
tonydf1 小时前
一次由组件并发引发的类“缓存击穿”问题排查与修复
redis·后端·架构
golang学习记1 小时前
Git 2.54 来了,这个新命令让我终于敢重写历史了
git·后端
Lyyaoo.1 小时前
【JAVA网络面经】应用层协议
java·开发语言·网络
二月龙1 小时前
谁说Python不能做高并发?用asyncio+FastAPI吞吐量提高10倍
后端