第一部分:JDBC操作数据库原理
1.1 为什么需要JDBC
⭐ 老师强调:Java程序不能直接操作数据库,必须通过**驱动(Driver)**来连接。就像电脑需装驱动才能用打印机、鼠标一样,操作MySQL或Oracle也需安装各自厂商提供的数据库驱动。
问题: 各数据库厂商驱动方法不统一(如MySQL用A方法添加,Oracle用B方法添加),切换数据库需重新学习,工作量极大。
解决方案: Sun公司制定JDBC规范(Java Database Connectivity),规定统一接口规则。各厂商驱动只要满足规范,对外接口就完全一致。程序员按JDBC规则调用方法即可操作任何数据库。
1.2 JDBC核心流程
手动操作数据库的步骤:
- 有数据库
- 创建连接(主机IP、用户名、密码)
- 选择具体库
- 操作表数据
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) |
第二部分:数据库环境准备
2.1 Navicat使用
⭐ 老师强调 :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:主键,自动递增,不用手动填写name、age、sex:按需添加
⭐ 老师强调 :表名不能含中文,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 封装流程
步骤:
-
查询得到
ResultSet(二维表) -
逐行读取,每行封装为一个
String[](dataRow) -
将所有行存入
ArrayList<String[]> -
调用
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,初心好却没真正防住注入。