JDBC 梳理
SQL 注入是一种攻击手段:攻击者通过在输入框中插入恶意 SQL 代码,欺骗服务器执行非预期的 SQL 操作(如越权查询、删除数据等)。
一、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 及以上版本可省略该步骤(驱动类会自动注册)。
- 获取连接:通过
DriverManager的getConnection方法,传入数据库 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 的对象(
createStatement、prepareStatement等)。 - 管理事务(
setAutoCommit、commit、rollback等方法)。
事务管理示例:
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是数据类型,如getInt、getString等)。
示例:
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 注入就被阻止了。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)