JDBC数据库操作与SQL注入防范

第一部分:JDBC操作数据库原理

1.1 为什么需要JDBC

老师强调:Java程序不能直接操作数据库,必须通过**驱动(Driver)**来连接。就像电脑需装驱动才能用打印机、鼠标一样,操作MySQL或Oracle也需安装各自厂商提供的数据库驱动。

问题: 各数据库厂商驱动方法不统一(如MySQL用A方法添加,Oracle用B方法添加),切换数据库需重新学习,工作量极大。

解决方案: Sun公司制定JDBC规范(Java Database Connectivity),规定统一接口规则。各厂商驱动只要满足规范,对外接口就完全一致。程序员按JDBC规则调用方法即可操作任何数据库。

1.2 JDBC核心流程

手动操作数据库的步骤:

  1. 有数据库
  2. 创建连接(主机IP、用户名、密码)
  3. 选择具体库
  4. 操作表数据

JDBC抽象了这套动作:

复制代码
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 创建连接(DriverManager驱动管理类)
String url = "jdbc:mysql://localhost:3306/qcby07";
String username = "root";
String password = "2020";
Connection connection = DriverManager.getConnection(url, username, password);

// 3. 创建执行SQL的对象
Statement statement = connection.createStatement();

// 4. 执行SQL语句
String sql = "select * from student";
ResultSet resultSet = statement.executeQuery(sql);

// 5. 处理结果集
while (resultSet.next()) {
    // 逐行读取数据
}

// 6. 释放资源(倒序关闭)
resultSet.close();
statement.close();
connection.close();

老师强调 :图形化工具(如Navicat)隐藏了加载驱动等步骤,但JDBC编码中加载驱动是固定且不可省略的。Connection是接口不能直接new,需通过DriverManager获取。

1.3 JDBC核心类和接口

类/接口 作用 获取方式
DriverManager 驱动管理类 直接调用静态方法
Connection 数据库连接(接口) DriverManager.getConnection()
Statement 执行SQL的对象 connection.createStatement()
PreparedStatement 预编译SQL(防注入) connection.prepareStatement(sql)
ResultSet 查询结果集(二维表) statement.executeQuery(sql)

第二部分:数据库环境准备

老师强调 :Navicat是数据库可视化工具,不是数据库本体。真实数据存在MySQL中,Navicat只是方便查看和操作的客户端界面。

连接设置:

  • 主机名:localhost(本地)
  • 用户名:root
  • 密码:2020(未修改的情况下)

2.2 创建数据库

复制代码
-- 库名不能含中文,字符集必须用utf8mb4
CREATE DATABASE qcby07 CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

老师强调 :字符集必须选utf8mb4,它能存小图标等特殊字符,普通utf8存不了。排序规则选utf8mb4_bin

2.3 创建表

复制代码
CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,  -- 主键、非空、自动递增
    name VARCHAR(50),
    age INT,
    sex VARCHAR(10)
);

字段说明:

  • id:主键,自动递增,不用手动填写
  • nameagesex:按需添加

老师强调表名不能含中文,id设自动递增保证唯一标识。右键"设计表"是配置结构,双击打开才是编辑数据,两者易混淆。

第三部分:SQL基础操作(增删改查)

3.1 查询(SELECT)

复制代码
-- 查询所有字段
SELECT * FROM student;

-- 查询指定字段
SELECT id, name, age FROM student;

-- 带条件查询
SELECT * FROM student WHERE name = '张三' AND sex = '男';

老师强调:SQL关键字大小写不区分,但习惯统一大写或小写,不要混写。选中单条语句再执行,避免误跑后续代码。

3.2 添加(INSERT)

复制代码
-- id自动递增,不用写
INSERT INTO student (name, age, sex) VALUES ('张三', 20, '男');

3.3 修改(UPDATE)

复制代码
-- 必须加WHERE条件,否则全表被改
UPDATE student SET name = '李四', age = 21 WHERE id = 3;

老师强调 :UPDATE必须加WHERE条件,否则整张表所有记录都会被修改!

3.4 删除(DELETE)

复制代码
-- 必须加WHERE条件,否则全表被删
DELETE FROM student WHERE id = 9;

老师强调 :DELETE必须加WHERE条件,否则整张表数据全部删除!删除后id不会补位或重排,新记录id继续递增。

增删改的返回值: 都是受影响的行数(int类型),而非数据本身。

第四部分:JDBC增删改查完整实现

4.1 加载驱动与创建连接

复制代码
// 加载驱动(固定写法)
Class.forName("com.mysql.cj.jdbc.Driver");

// 连接信息
String url = "jdbc:mysql://localhost:3306/qcby07";
String username = "root";
String password = "2020";

// 创建连接
Connection connection = DriverManager.getConnection(url, username, password);

驱动类版本区别:

  • MySQL 5.x:com.mysql.jdbc.Driver
  • MySQL 8.x:com.mysql.cj.jdbc.Driver

4.2 查询(SELECT)

复制代码
public static void search() throws Exception {
    // 1. 加载驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    
    // 2. 创建连接
    String url = "jdbc:mysql://localhost:3306/qcby07";
    String username = "root";
    String password = "2020";
    Connection connection = DriverManager.getConnection(url, username, password);
    
    // 3. 创建执行SQL的对象
    Statement statement = connection.createStatement();
    
    // 4. 执行查询
    String sql = "select * from student";
    ResultSet resultSet = statement.executeQuery(sql);
    
    // 5. 处理结果集(逐行读取)
    while (resultSet.next()) {
        String id = resultSet.getString("id");
        String name = resultSet.getString("name");
        String age = resultSet.getString("age");
        String sex = resultSet.getString("sex");
        System.out.println("id:" + id + " name:" + name + " age:" + age + " sex:" + sex);
    }
    
    // 6. 释放资源(倒序:先rs再stmt最后conn)
    if (resultSet != null) resultSet.close();
    if (statement != null) statement.close();
    if (connection != null) connection.close();
}

老师强调executeQuery()用于查询,返回ResultSet(二维表)。ResultSet初始指向表头行,需用next()跳到第一行数据才能读取。getString()可用列名或序号(1、2、3...)。

4.3 添加/修改/删除(UPDATE操作)

复制代码
public static void change() throws Exception {
    // 1-3步同查询
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/qcby07";
    String username = "root";
    String password = "2020";
    Connection connection = DriverManager.getConnection(url, username, password);
    Statement statement = connection.createStatement();
    
    // 4. 执行更新(增删改都用executeUpdate)
    String sql = "delete from student where id = 9";
    int num = statement.executeUpdate(sql);
    System.out.println("受影响的行数:" + num);
    
    // 5. 释放资源
    if (statement != null) statement.close();
    if (connection != null) connection.close();
}

老师强调 :添加、删除、修改对数据库而言都是"数据变更",统一用executeUpdate(),返回受影响的行数 (int)。增删改没有ResultSet,释放资源时只需关Statement和Connection。

第五部分:数据封装为JSON

5.1 为什么需要封装

老师强调 :Servlet只能返回字符串 ,无法直接返回JSON对象。需将ResultSet查询结果封装为JSON字符串再返回给前端。

5.2 封装流程

步骤:

  1. 查询得到ResultSet(二维表)

  2. 逐行读取,每行封装为一个String[](dataRow)

  3. 将所有行存入ArrayList<String[]>

  4. 调用listToJson()将集合转为JSON字符串

    public static String getJsonBySql(String sql, String[] colums) {
    ArrayList<String[]> result = new ArrayList<String[]>();
    DBConnection db = new DBConnection();
    try {
    Statement stmt = db.conn.createStatement();
    ResultSet rs = stmt.executeQuery(sql);

    复制代码
         while (rs.next()) {
             String[] dataRow = new String[colums.length];
             for (int i = 0; i < dataRow.length; i++) {
                 dataRow[i] = rs.getString(colums[i]);  // 按列名取字段
             }
             result.add(dataRow);
         }
         rs.close();
         db.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }
     return listToJson(result, colums);

    }

5.3 拼接JSON字符串

复制代码
public static String listToJson(ArrayList<String[]> list, String[] colums) {
    String jsonStr = "{\"code\":0,\"msg\":\"ok\",\"data\":[";
    
    for (int i = 0; i < list.size(); i++) {
        String arr = "{";
        for (int j = 0; j < list.get(0).length; j++) {
            // 处理null值
            if (list.get(i)[j] == null || "NULL".equals(list.get(i)[j])) {
                arr += "\"" + colums[j] + "\":\"\"";
            } else {
                arr += "\"" + colums[j] + "\":";
                // 转义双引号
                arr += "\"" + list.get(i)[j].replace("\"", "\\\"") + "\"";
            }
            if (j < list.get(0).length - 1) arr += ",";
        }
        arr += "}";
        if (i < list.size() - 1) arr += ",";
        jsonStr += arr;
    }
    jsonStr += "]}";
    return jsonStr;
}

返回的JSON格式:

复制代码
{
    "code": 0,
    "msg": "ok",
    "data": [
        {"id": "1", "name": "张三", "age": "20", "sex": "男"},
        {"id": "2", "name": "李四", "age": "21", "sex": "女"}
    ]
}

老师强调 :JDBC无法直接返回JSON,必须硬拼接 字符串。要注意双引号转义(\")和换行符处理,否则JSON格式不合法。

第六部分:MysqlUtil工具类各方法使用示例

6.1 DBConnection ------ 连接工具类

复制代码
package com.qcby.db;

import java.sql.Connection;
import java.sql.DriverManager;

public class DBConnection {
    String driver = "com.mysql.cj.jdbc.Driver";  // 8.x版本
    String url = "jdbc:mysql://127.0.0.1:3306/qcby07";
    String user = "root";
    String password = "2020";
    public Connection conn;

    public DBConnection() {
        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, user, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

6.2 MysqlUtil ------ 完整工具类

复制代码
package com.qcby.db;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public class MysqlUtil {

    // ============ 添加 ============
    public static int add(String sql) {
        int i = 0;
        DBConnection db = new DBConnection();
        try {
            PreparedStatement preStmt = db.conn.prepareStatement(sql);
            i = preStmt.executeUpdate();
            preStmt.close();
            db.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return i;
    }

    // ============ 修改 ============
    public static int update(String sql) {
        int i = 0;
        DBConnection db = new DBConnection();
        try {
            PreparedStatement preStmt = db.conn.prepareStatement(sql);
            i = preStmt.executeUpdate();
            preStmt.close();
            db.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return i;
    }

    // ============ 删除 ============
    public static int del(String sql) {
        int i = 0;
        DBConnection db = new DBConnection();
        try {
            PreparedStatement preStmt = db.conn.prepareStatement(sql);
            i = preStmt.executeUpdate();
            preStmt.close();
            db.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return i;
    }

    // ============ 查询数量 ============
    public static int getCount(String sql) {
        int sum = 0;
        DBConnection db = new DBConnection();
        try {
            Statement stmt = db.conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                sum += rs.getInt(1);  // 取第一列
            }
            rs.close();
            db.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sum;
    }

    // ============ 查询并返回JSON ============
    public static String getJsonBySql(String sql, String[] colums) {
        ArrayList<String[]> result = new ArrayList<String[]>();
        DBConnection db = new DBConnection();
        try {
            Statement stmt = db.conn.createStatement();
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()) {
                String[] dataRow = new String[colums.length];
                for (int i = 0; i < dataRow.length; i++) {
                    dataRow[i] = rs.getString(colums[i]);
                }
                result.add(dataRow);
            }
            rs.close();
            db.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return listToJson(result, colums);
    }

    // ============ 集合转JSON ============
    public static String listToJson(ArrayList<String[]> list, String[] colums) {
        String jsonStr = "{\"code\":0,\"msg\":\"ok\",\"data\":[\";
        for (int i = 0; i < list.size(); i++) {
            String arr = "{";
            for (int j = 0; j < list.get(0).length; j++) {
                if (list.get(i)[j] == null || "NULL".equals(list.get(i)[j])) {
                    arr += "\"" + colums[j] + "\":\"\"";
                } else {
                    arr += "\"" + colums[j] + "\":";
                    arr += "\"" + list.get(i)[j].replace("\"", "\\\"") + "\"";
                }
                if (j < list.get(0).length - 1) arr += ",";
            }
            arr += "}";
            if (i < list.size() - 1) arr += ",";
            jsonStr += arr;
        }
        jsonStr += "]}";
        return jsonStr;
    }
}

6.3 各方法使用示例

6.3.1 添加(add)
复制代码
// 使用场景:新增一条学生记录
String sql = "INSERT INTO student (name, age, sex) VALUES ('王五', 22, '男')";
int result = MysqlUtil.add(sql);
System.out.println("添加结果,受影响行数:" + result);
// 输出:添加结果,受影响行数:1
6.3.2 修改(update)
复制代码
// 使用场景:修改id为5的学生姓名
String sql = "UPDATE student SET name = '赵六', age = 23 WHERE id = 5";
int result = MysqlUtil.update(sql);
System.out.println("修改结果,受影响行数:" + result);
// 输出:修改结果,受影响行数:1
6.3.3 删除(del)
复制代码
// 使用场景:删除id为9的学生
String sql = "DELETE FROM student WHERE id = 9";
int result = MysqlUtil.del(sql);
System.out.println("删除结果,受影响行数:" + result);
// 输出:删除结果,受影响行数:1
6.3.4 查询数量(getCount)
复制代码
// 使用场景:统计学生总数
String sql = "SELECT COUNT(*) FROM student";
int count = MysqlUtil.getCount(sql);
System.out.println("学生总数:" + count);
// 输出:学生总数:8

// 使用场景:统计男生人数
String sql2 = "SELECT COUNT(*) FROM student WHERE sex = '男'";
int maleCount = MysqlUtil.getCount(sql2);
System.out.println("男生人数:" + maleCount);
6.3.5 查询并返回JSON(getJsonBySql)
复制代码
// 使用场景:查询所有学生,封装为JSON返回前端
String[] columns = {"id", "name", "age", "sex"};
String sql = "SELECT id, name, age, sex FROM student";
String json = MysqlUtil.getJsonBySql(sql, columns);
System.out.println(json);

// 输出示例:
// {"code":0,"msg":"ok","data":[
//   {"id":"1","name":"张三","age":"20","sex":"男"},
//   {"id":"2","name":"李四","age":"21","sex":"女"},
//   {"id":"3","name":"王五","age":"22","sex":"男"}
// ]}

// 使用场景:只查询id和name
String[] columns2 = {"id", "name"};
String sql2 = "SELECT id, name FROM student WHERE sex = '男'";
String json2 = MysqlUtil.getJsonBySql(sql2, columns2);
System.out.println(json2);

// 输出示例:
// {"code":0,"msg":"ok","data":[
//   {"id":"1","name":"张三"},
//   {"id":"3","name":"王五"}
// ]}

6.4 Servlet中调用MysqlUtil示例

复制代码
@WebServlet("/StudentList")
public class StudentList extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        // 1. 查询数据
        String[] columns = {"id", "name", "age", "sex"};
        String sql = "SELECT id, name, age, sex FROM student";
        String json = MysqlUtil.getJsonBySql(sql, columns);
        
        // 2. 设置编码和返回格式
        response.setContentType("application/json;charset=utf-8");
        
        // 3. 返回JSON
        response.getWriter().write(json);
    }
}

6.5 关于SQL注入的注意事项

老师强调 :MysqlUtil中的add()update()del()方法虽用了PreparedStatement,但传入的是已拼接好的SQL字符串 ,并未真正使用占位符传参。这样无法防止SQL注入

复制代码
// ❌ 当前MysqlUtil的使用方式(有注入风险)
String sql = "DELETE FROM student WHERE id = " + userInput;  // 拼接
MysqlUtil.del(sql);  // 拼接后传入,PreparedStatement无法防注入

// ✅ 正确的防注入方式
String sql = "DELETE FROM student WHERE id = ?";  // 占位符
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, Integer.parseInt(userInput));  // 单独传参
ps.executeUpdate();

老师强调 :真正防注入必须传带占位符的SQL模板 并单独赋值。若传已拼接好的SQL字符串,PreparedStatement的预编译机制完全失效。课后思考题:完善MysqlUtil,使其真正支持防SQL注入。

第七部分:SQL注入与PreparedStatement

7.1 什么是SQL注入

老师强调 :SQL注入是攻击者通过输入框恶意构造特殊字符(如' or '1'='1),利用字符串拼接篡改SQL语义,从而非法获取或破坏数据。

拼接SQL的登录示例(有漏洞):

复制代码
-- 正常SQL
SELECT * FROM student WHERE name = '张三' AND sex = '男';

-- 攻击者输入:name = ' or '1'='1
-- 拼接后变成:
SELECT * FROM student WHERE name = '' or '1'='1' AND sex = '男';
-- '1'='1'永远为true,绕过登录验证!

老师强调 :SQL注入的本质是用户输入成了SQL语句的一部分,而非仅作参数值。拼接像把原料乱搅一起煮,容易串味出错。

7.2 PreparedStatement ------ 预编译防范

老师强调 :PreparedStatement先预编译 SQL结构(固定逻辑),用户输入只作参数填充,不会被当作指令执行,从根源防注入。就像"先印空白表格模板,再填内容",而非直接交写满的纸。

使用方式:

复制代码
public static void login(String sql, String username, String usersex) throws Exception {
    // 1-2步同JDBC
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/qcby07";
    String user = "root";
    String password = "2020";
    Connection connection = DriverManager.getConnection(url, user, password);
    
    // 3. 创建PreparedStatement(预编译SQL)
    PreparedStatement statement = connection.prepareStatement(sql);
    // sql = "select * from student where name = ? and sex = ?"
    
    // 4. 给占位符赋值(按顺序)
    statement.setString(1, username);   // 第一个?赋值
    statement.setString(2, usersex);    // 第二个?赋值
    
    // 5. 执行查询(不传sql)
    ResultSet resultSet = statement.executeQuery();
    
    // 6. 处理结果
    if (resultSet.next()) {
        System.out.println("登录成功");
    } else {
        System.out.println("登录失败");
    }
    
    // 7. 释放资源...
}

Statement vs PreparedStatement:

对比 Statement PreparedStatement
SQL处理 直接拼接字符串 预编译 + 占位符
防SQL注入 ❌ 不能 ✅ 能
性能 每次编译 预编译一次,多次执行
使用场景 简单固定SQL 动态传参SQL

老师强调 :真正防注入必须传带占位符的SQL模板 并单独赋值,不能传已拼接好的SQL字符串。若图通用传拼好的SQL,初心好却没真正防住注入

相关推荐
CCYe、1 小时前
新模型上线、旧模型下线:企业AI网关如何管住模型版本
java·网络·数据库·人工智能
疯狂打码的少年1 小时前
【数据库技术】SQL嵌套查询(IN/EXISTS/ANY/ALL)
数据库·笔记·sql
清晨0012 小时前
国产化数据库替代方案:openGauss 详解与 MySQL 平滑迁移实战
数据库·mysql
λqaq72 小时前
MongoDB数据库基础 :非关系型数据库入门与常用操作
数据库·学习·mongodb·nosql
词却2 小时前
数据库基础:TCL事务与DDL视图
数据库
码农小麦2 小时前
LangChain 1.3.18 + DeepSeek-v4-flash 工具调用踩坑实录
网络·数据库·langchain
FfHUCisI2 小时前
Golang 切片扩容策略
开发语言·数据库·golang
en.en..2 小时前
Ubuntu嵌入式开发 export环境变量
java·开发语言·数据库
攻城有术2 小时前
专项攻克——重写 Redis 依赖包方法的 6 种实现方式
java·数据库·redis·bootstrap