JDBC 入门详解(附工具类与防 SQL 注入)
一、JDBC 核心概念深度解析
JDBC(Java DataBase Connectivity)并非具体实现,而是 Java 官方定义的数据库操作标准接口规范。其设计目标是解决 “不同数据库厂商接口不统一” 的问题 ——Java 通过统一接口定义操作逻辑,MySQL、Oracle 等厂商则提供符合接口的实现类(即 “驱动”),开发者只需掌握接口用法,即可用相同的 Java 代码操作任意数据库,无需关注底层厂商差异。
1. 核心组成:接口与驱动的协作
- 接口层:由 JDK 提供,核心接口包括
Connection(数据库连接)、Statement(SQL 执行器)、ResultSet(结果集)等,定义了 “连接数据库、执行 SQL、处理结果” 的标准方法。 - 驱动层:由数据库厂商提供,是接口的具体实现。例如 MySQL 的驱动是
mysql-connector-java系列 Jar 包,Oracle 的驱动是ojdbc系列 Jar 包,开发者必须导入对应驱动才能建立连接。
2. 版本差异:MySQL 5 与 MySQL 8 驱动区别
MySQL 5 和 MySQL 8 的驱动在类路径、URL 参数、依赖版本上存在关键差异,直接影响 JDBC 代码的正确性,具体对比如下:
| 对比项 | MySQL 5 版本(以 5.1.13 为例) | MySQL 8 版本(以 8.0.30 为例) |
|---|---|---|
| 驱动 Jar 包名称 | mysql-connector-java-5.1.13-bin.jar | mysql-connector-java-8.0.30.jar |
| 驱动类全路径 | com.mysql.jdbc.Driver | com.mysql.cj.jdbc.Driver(需加cj) |
| URL 必填参数 | 无需额外参数(示例:jdbc:mysql:///jdbcdemo) | 必须加serverTimezone(示例:jdbc:mysql:///jdbcdemo?serverTimezone=UTC) |
| 自动加载驱动 | 需手动执行Class.forName()加载 | 支持 SPI 自动加载(可省略Class.forName(),但建议显式加载以兼容低版本 JDK) |
| 依赖兼容性 | 支持 JDK 5 及以上 | 推荐 JDK 8 及以上(低版本 JDK 可能存在兼容性问题) |
二、JDBC 入门核心步骤(MySQL 5/8 双版本适配)
JDBC 操作的本质是 “按固定流程调用接口方法”,无论 MySQL 5 还是 8,核心流程均为 “加载驱动→获取连接→执行 SQL→处理结果→释放资源”,但需根据版本调整细节。
1. 环境准备:创建数据库与表(通用)
首先在 MySQL 中创建测试数据库和表,执行以下 SQL 脚本(5/8 版本通用):
-- 1.创建数据库jdbcdemo
create database if not exists jdbcdemo character set utf8mb4 collate utf8mb4_general_ci;
-- 2.使用数据库
use jdbcdemo;
-- 3.创建用户表t_user(包含主键自增、用户名、密码、邮箱字段)
create table if not exists t_user(id int primary key auto_increment comment '用户ID(自增主键)',username varchar(30) not null comment '用户名',password varchar(30) not null comment '密码',email varchar(50) comment '邮箱'
) comment '用户表';
-- 4.插入测试数据
insert into t_user values
(null,'aaa','123','aaa@163.com'),
(null,'bbb','456','bbb@163.com'),
(null,'ccc','789','ccc@163.com');
2. 开发五步走(双版本代码对比)
步骤 1:导入驱动 Jar 包
- MySQL 5:下载
mysql-connector-java-5.1.13-bin.jar,放入项目lib目录(或通过 Maven 依赖:<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.13</version></dependency>)。 - MySQL 8:下载
mysql-connector-java-8.0.30.jar,放入项目lib目录(或通过 Maven 依赖:<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency>)。
步骤 2:加载驱动(版本差异核心点)
- MySQL 5:必须通过
Class.forName()加载驱动类,否则无法初始化驱动。// MySQL 5加载驱动 Class.forName("com.mysql.jdbc.Driver"); - MySQL 8:驱动类路径变为
com.mysql.cj.jdbc.Driver,且支持 SPI 自动加载(JDK 6 + 可省略此步骤),但显式加载更稳妥。// MySQL 8加载驱动(必须加cj) Class.forName("com.mysql.cj.jdbc.Driver");
步骤 3:获取数据库连接(URL 参数差异)
通过DriverManager.getConnection(url, username, password)获取Connection对象,url是版本差异的关键:
- MySQL 5 URL 格式:
jdbc:mysql://[主机IP]:[端口号]/[数据库名],本地数据库可省略主机IP:端口号(默认localhost:3306)。// MySQL 5获取连接(本地数据库简写) String url = "jdbc:mysql:///jdbcdemo"; // 完整写法:jdbc:mysql://localhost:3306/jdbcdemo String username = "root"; // 你的MySQL用户名 String password = "root"; // 你的MySQL密码 Connection conn = DriverManager.getConnection(url, username, password); - MySQL 8 URL 格式:必须添加
serverTimezone参数(解决时区错误),可选添加useSSL=false(开发环境禁用 SSL 加密,避免证书问题)。// MySQL 8获取连接(带时区参数) String url = "jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false"; // 完整写法:jdbc:mysql://localhost:3306/jdbcdemo?serverTimezone=UTC&useSSL=false String username = "root"; String password = "root"; Connection conn = DriverManager.getConnection(url, username, password);
步骤 4:执行 SQL 语句(分查询 / 增删改)
根据 SQL 类型,使用Statement或PreparedStatement执行(推荐后者,防 SQL 注入):
- 查询操作:执行
select语句,返回ResultSet结果集,需遍历获取数据。// 1.编写SQL(查询t_user所有数据) String sql = "select id, username, password, email from t_user"; // 2.创建PreparedStatement对象(预编译SQL,防注入) PreparedStatement pstmt = conn.prepareStatement(sql); // 3.执行查询,获取结果集 ResultSet rs = pstmt.executeQuery(); // 4.遍历结果集(游标默认指向第一行前,next()移动游标并判断是否有数据) while (rs.next()) {// 按字段名获取数据(推荐,不受字段顺序影响)int id = rs.getInt("id"); String uname = rs.getString("username");String pwd = rs.getString("password");String email = rs.getString("email");// 按索引获取数据(索引从1开始,需与SQL字段顺序一致)// int id = rs.getInt(1);// String uname = rs.getString(2);System.out.printf("ID:%d,用户名:%s,密码:%s,邮箱:%s%n", id, uname, pwd, email); } - 增删改操作:执行
insert/update/delete语句,返回受影响的行数(int 类型)。// 示例:新增用户(MySQL 5/8通用) String sql = "insert into t_user(username, password, email) values(?, ?, ?)"; PreparedStatement pstmt = conn.prepareStatement(sql); // 给占位符赋值(?的索引从1开始,类型需与字段匹配) pstmt.setString(1, "ddd"); // 第一个?:username pstmt.setString(2, "101112"); // 第二个?:password pstmt.setString(3, "ddd@163.com"); // 第三个?:email // 执行增删改,获取受影响行数 int rows = pstmt.executeUpdate(); System.out.println("新增成功,受影响行数:" + rows); // 输出1表示新增1条数据
步骤 5:释放资源(必须执行,避免内存泄漏)
Connection(连接)、PreparedStatement(执行器)、ResultSet(结果集)都是 “稀缺资源”,使用后必须关闭,且关闭顺序为 “ResultSet → PreparedStatement → Connection”(后创建的先关闭)。建议在finally块中处理(确保无论是否报错都会执行):
// 释放资源(通用代码)
finally {// 关闭ResultSetif (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}// 关闭PreparedStatementif (pstmt != null) {try {pstmt.close();} catch (SQLException e) {e.printStackTrace();}}// 关闭Connection(最重要,不关闭会导致数据库连接耗尽)if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}
}
三、核心 API 详解(掌握这些,JDBC 就会了)
1. DriverManager 类:驱动管理与连接获取
DriverManager是 JDBC 的 “入口类”,主要负责加载驱动和创建数据库连接:
- 加载驱动:通过
Class.forName(驱动类全路径)触发驱动类的静态代码块,自动向DriverManager注册驱动(MySQL 5 必须显式执行,MySQL 8 可省略但建议保留)。 - 获取连接:
static Connection getConnection(String url, String user, String password)是核心方法,3 个参数的含义:url:数据库连接地址,格式随 MySQL 版本变化(见步骤 3)。user:MySQL 用户名(如root)。password:MySQL 密码(如root)。
2. Connection 接口:数据库连接的 “总开关”
Connection代表与数据库的 “一次连接会话”,是 JDBC 中最重要的接口之一,核心功能包括:
- 创建 SQL 执行器:
createStatement():创建Statement对象(不推荐,有 SQL 注入风险)。prepareStatement(String sql):创建PreparedStatement对象(推荐,支持预编译,防注入)。
- 事务管理:
setAutoCommit(boolean autoCommit):设置是否自动提交事务(默认true,即执行 SQL 后自动提交;手动事务需设为false)。commit():提交事务(手动事务中,所有 SQL 执行成功后调用)。rollback():回滚事务(手动事务中,SQL 执行失败时调用,恢复到事务开始前状态)。
3. PreparedStatement 接口:防注入的 “安全执行器”
PreparedStatement是Statement的子接口,核心优势是预编译 SQL,彻底解决 SQL 注入漏洞:
- 预编译原理:创建
PreparedStatement时,会将 SQL 语句(含?占位符)发送到 MySQL 服务器,服务器先对 SQL 进行 “语法解析和编译”,生成固定的执行计划;后续传入的参数(给?赋值)仅作为 “纯数据” 处理,不会被当作 SQL 语法解析,因此无法篡改 SQL 逻辑。 - 核心方法:
setXxx(int parameterIndex, Xxx value):给?赋值(Xxx是数据类型,如setString、setInt,parameterIndex是?的索引,从 1 开始)。executeQuery():执行查询 SQL,返回ResultSet(无参数,因 SQL 已预编译)。executeUpdate():执行增删改 SQL,返回受影响行数(无参数)。
4. ResultSet 接口:查询结果的 “容器”
ResultSet用于封装查询结果(类似 “内存中的表格”),核心特性和方法:
- 游标机制:内部维护一个 “游标”,默认指向 “第一行数据之前”;调用
next()方法可将游标向下移动一行,返回true表示游标指向有效数据,false表示已到结果集末尾。 - 获取数据:
- 按字段名:
getString(String columnLabel)、getInt(String columnLabel)(如rs.getString("username")),优点是 “与 SQL 字段顺序无关”,代码更健壮。 - 按索引:
getString(int columnIndex)、getInt(int columnIndex)(如rs.getInt(1)),优点是效率略高,但需与 SQL 字段顺序严格一致,维护成本高。 - 通用方法:
getObject(String columnLabel)或getObject(int columnIndex),返回Object类型,需手动强转(适用于不确定字段类型的场景)。
- 按字段名:
四、实战优化:JDBC 工具类封装(双版本适配)
实际开发中,“加载驱动、获取连接、释放资源” 是重复代码,封装成工具类可大幅减少冗余。以下是支持 MySQL 5/8 的通用工具类,通过配置文件实现 “参数可配置”。
1. 编写配置文件(db.properties)
在项目src目录下创建db.properties文件,存储驱动类、URL、用户名、密码(根据 MySQL 版本修改对应值):
# MySQL 5配置(注释掉MySQL 8配置即可使用)
# driver=com.mysql.jdbc.Driver
# url=jdbc:mysql:///jdbcdemo# MySQL 8配置(注释掉MySQL 5配置即可使用)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///jdbcdemo?serverTimezone=UTC&useSSL=false# 数据库账号密码(根据自己的MySQL修改)
username=root
password=root
2. 封装工具类(JDBCUtils.java)
工具类采用 “静态代码块加载配置文件 + 静态方法提供连接 / 释放资源” 的设计,确保配置只加载一次,连接获取更高效:
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;public class JDBCUtils {// 静态变量存储配置信息(类加载时初始化,全局共享)private static String driver;private static String url;private static String username;private static String password;// 静态代码块:加载配置文件(类加载时执行一次,避免重复加载)static {try {// 1.创建Properties对象(用于读取key-value格式的配置文件)Properties props = new Properties();// 2.通过类加载器获取配置文件的输入流(src目录下的文件可直接通过类加载器读取)InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties");// 3.加载配置文件到Propertiesprops.load(is);// 4.读取配置信息并赋值给静态变量driver = props.getProperty("driver");url = props.getProperty("url");username = props.getProperty("username");password = props.getProperty("password");// 5.加载驱动(MySQL 5必须,MySQL 8可选但建议显式加载)Class.forName(driver);} catch (IOException | ClassNotFoundException e) {// 抛出运行时异常,终止程序(配置加载或驱动加载失败,程序无法继续)throw new RuntimeException("JDBC工具类初始化失败:" + e.getMessage(), e);}}/*** 获取数据库连接* @return Connection对象* @throws SQLException 连接获取失败时抛出*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, username, password);}/*** 释放资源(适用于查询操作:需关闭ResultSet、PreparedStatement、Connection)*/public static void close(ResultSet rs, PreparedStatement pstmt, Connection conn) {// 关闭ResultSetif (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}// 关闭PreparedStatementif (pstmt != null) {try {pstmt.close();} catch (SQLException e) {e.printStackTrace();}}// 关闭Connectionif (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}/*** 释放资源(适用于增删改操作:无需关闭ResultSet)*/public static void close(PreparedStatement pstmt, Connection conn) {// 调用重载方法,减少代码冗余close(null, pstmt, conn);}
}
3. 工具类使用示例(查询 t_user 表)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class JDBCUtilsDemo {public static void main(String[] args) {// 声明资源变量(作用域扩大到finally)Connection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;try {// 1.通过工具类获取连接(无需关心驱动和URL细节)conn = JDBCUtils.getConnection();// 2.编写SQLString sql = "select id, username, email from t_user where password = ?";// 3.创建PreparedStatementpstmt = conn.prepareStatement(sql);// 4.给占位符赋值pstmt.setString(1, "123");// 5.执行查询rs = pstmt.executeQuery();// 6.处理结果System.out.println("密码为123的用户:");while (rs.next()) {System.out.printf("ID:%d,用户名:%s,邮箱:%s%n", rs.getInt("id"), rs.getString("username"), rs.getString("email"));}} catch (SQLException e) {e.printStackTrace();} finally {// 7.通过工具类释放资源(无需写重复的关闭逻辑)JDBCUtils.close(rs, pstmt, conn);}}
}
五、常见问题与解决方案(MySQL 8 重点)
1. MySQL 8 时区错误(最常见)
- 错误信息:
The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone。 - 原因:MySQL 8 默认时区与 Java 虚拟机(JVM)时区不一致,且必须显式指定时区。
- 解决方案:在 URL 中添加
serverTimezone参数,可选值:UTC:世界协调时间(通用,推荐)。Asia/Shanghai:中国标准时间(与 UTC+8 一致,更符合国内场景)。- 示例:
jdbc:mysql:///jdbcdemo?serverTimezone=Asia/Shanghai&useSSL=false。
2. MySQL 8 SSL 连接警告
- 警告信息:
Establishing SSL connection without server's identity verification is not recommended。 - 原因:MySQL 8 默认开启 SSL 加密连接,开发环境中若未配置 SSL 证书,会触发安全警告。
- 解决方案:在 URL 中添加
useSSL=false,禁用 SSL 连接(生产环境需配置 SSL 证书,开启useSSL=true)。
3. 驱动类路径错误(MySQL 5→8 迁移时)
- 错误信息:
ClassNotFoundException: com.mysql.jdbc.Driver(使用 MySQL 8 时)。 - 原因:MySQL 8 的驱动类路径已改为
com.mysql.cj.jdbc.Driver,仍使用旧路径会导致类找不到。 - 解决方案:修改配置文件中的
driver值为com.mysql.cj.jdbc.Driver,并确保导入的是 MySQL 8 版本的驱动 Jar 包。
六、总结
JDBC 是 Java 操作数据库的 “基础核心技术”,掌握它不仅能完成简单的数据库交互,更是学习 MyBatis、Spring JDBC 等框架的前提。核心要点总结:
- 版本适配:MySQL 5 与 8 的驱动类、URL 参数差异是关键,需重点区分。
- 核心流程:“加载驱动→获取连接→执行 SQL→处理结果→释放资源” 五步是不变的核心,其中 “释放资源” 和 “用 PreparedStatement 防注入” 是必须遵守的规范。
- 工具类封装:通过配置文件 + 工具类减少重复代码,是实际开发的必备技巧。
