《Java 程序设计》第 16 章 - JDBC 数据库编程
前言
在当今的软件开发中,几乎所有应用程序都需要与数据库进行交互。无论是小型应用还是大型企业系统,数据的存储、检索和管理都是核心功能。JDBC(Java Database Connectivity)作为 Java 语言访问数据库的标准接口,为开发者提供了一种统一的方式来操作各种关系型数据库。
本章将详细介绍 JDBC 数据库编程的相关知识,从数据库基础知识到 JDBC API 的具体使用,再到实际应用案例,帮助读者掌握 Java 操作数据库的核心技能。
思维导图

16.1 数据库系统简介
数据库系统(Database System)是由数据库、数据库管理系统(DBMS)、应用程序和数据库管理员(DBA)组成的系统。它的主要功能是存储、管理和检索数据,同时保证数据的安全性、完整性和一致性。
16.1.1 关系数据库简述
关系数据库是目前应用最广泛的数据库类型,它以表格(Table)的形式组织数据,表格之间通过关系(Relationship)建立联系。
- 表(Table):由行(Row)和列(Column)组成,用于存储特定类型的数据
- 行(Row):也称为记录(Record),代表一条完整的数据
- 列(Column):也称为字段(Field),代表数据的一个属性
- 主键(Primary Key):用于唯一标识表中的每条记录,不能重复
- 外键(Foreign Key):用于建立表之间的关系,指向另一个表的主键
关系数据库的主要特点是使用关系模型(二维表格模型)来组织数据,通过 SQL 语言进行操作。
16.1.2 数据库语言 SQL
SQL(Structured Query Language,结构化查询语言)是用于管理关系数据库的标准语言。它可以分为以下几类:
- 数据查询语言(DQL):用于查询数据,主要命令是
SELECT - 数据操纵语言(DML):用于插入、更新和删除数据,主要命令有
INSERT、UPDATE、DELETE - 数据定义语言(DDL):用于创建和修改数据库对象,主要命令有
CREATE、ALTER、DROP - 数据控制语言(DCL):用于控制数据访问权限,主要命令有
GRANT、REVOKE
常用 SQL 语句示例:
-- 创建表
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
gender VARCHAR(10),
major VARCHAR(50)
);
-- 插入数据
INSERT INTO students (name, age, gender, major) VALUES ('张三', 20, '男', '计算机科学');
-- 查询数据
SELECT * FROM students WHERE age > 18;
-- 更新数据
UPDATE students SET age = 21 WHERE name = '张三';
-- 删除数据
DELETE FROM students WHERE id = 1;
16.2 MySQL 数据库
MySQL 是一种开源的关系型数据库管理系统,由于其开源免费、性能优良、易于使用等特点,被广泛应用于各种 Web 应用中。
16.2.1 MySQL 的下载与安装
-
下载 MySQL
- 访问 MySQL 官方网站:https://dev.mysql.com/downloads/mysql/
- 根据操作系统选择合适的版本下载,推荐下载 MySQL Community Server(社区版)
-
安装 MySQL
- Windows 系统:运行下载的安装文件,按照向导进行安装,可以选择 "Developer Default" 安装类型
- macOS 系统:可以使用 DMG 文件安装,或者通过 Homebrew 安装
- Linux 系统:可以使用包管理器安装,如
apt-get install mysql-server(Ubuntu)
-
配置 MySQL
- 安装过程中需要设置 root 用户密码
- 可以选择是否允许远程访问
- 配置端口号(默认 3306)
16.2.2 使用 MySQL 命令行工具
安装完成后,可以通过命令行工具操作 MySQL:
-
登录 MySQL
mysql -u root -p输入密码后即可登录
-
常用命令
-- 显示所有数据库 SHOW DATABASES; -- 创建数据库 CREATE DATABASE mydb; -- 使用数据库 USE mydb; -- 显示当前数据库中的所有表 SHOW TABLES; -- 查看表结构 DESCRIBE students; -- 退出MySQL EXIT;
16.2.3 使用 Navicat 操作数据库
Navicat 是一款功能强大的数据库管理工具,支持多种数据库,包括 MySQL、Oracle、SQL Server 等。使用图形界面操作数据库,比命令行更直观方便。
-
连接数据库
- 打开 Navicat,点击 "连接" 按钮,选择 "MySQL"
- 输入连接名称、主机名(默认localhost)、端口号(默认 3306)、用户名和密码
- 点击 "测试连接",成功后点击 "确定"
-
创建数据库和表
- 在左侧导航栏中,右键点击连接名称,选择 "新建数据库"
- 输入数据库名称和字符集(推荐 utf8mb4)
- 双击数据库名称进入该数据库
- 右键点击 "表",选择 "新建表",设计表结构并保存
-
数据操作
- 可以通过 "查询" 标签执行 SQL 语句
- 可以通过 "表" 标签直接查看、添加、修改和删除数据
16.3 JDBC 体系结构
JDBC(Java Database Connectivity)是 Java 语言访问数据库的标准接口,它为 Java 开发者提供了一种统一的方式来访问各种关系型数据库。
16.3.1 JDBC 访问数据库
JDBC 访问数据库的基本原理是通过驱动程序(Driver)与数据库进行通信。不同的数据库需要使用相应的驱动程序。
JDBC 访问数据库的流程如下:

16.3.2 JDBC API 介绍
JDBC API 主要包含以下核心接口和类:
- DriverManager:用于管理数据库驱动程序,获取数据库连接
- Connection:代表与数据库的连接
- Statement:用于执行静态 SQL 语句
- PreparedStatement:用于执行预编译的 SQL 语句,是 Statement 的子接口
- CallableStatement:用于执行存储过程,是 PreparedStatement 的子接口
- ResultSet:代表 SQL 查询的结果集
- SQLException:处理数据库操作中可能出现的异常
这些接口和类都位于java.sql包中。
16.4 数据库访问步骤
使用 JDBC 访问数据库通常遵循以下步骤:
16.4.1 加载驱动程序
在使用 JDBC 访问数据库之前,需要先加载相应的数据库驱动程序。
对于 MySQL 8.0 及以上版本,驱动类为com.mysql.cj.jdbc.Driver,可以通过以下方式加载:
// 加载MySQL驱动
Class.forName("com.mysql.cj.jdbc.Driver");
注意:从 JDBC 4.0 开始,驱动程序可以自动加载,不需要显式调用
Class.forName()方法,但为了兼容性,通常还是会显式加载。
16.4.2 建立连接对象
通过DriverManager.getConnection()方法建立与数据库的连接,需要提供数据库 URL、用户名和密码。
MySQL 的 URL 格式为:jdbc:mysql://主机名:端口号/数据库名?参数
// 数据库连接信息
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "123456";
// 建立连接
Connection conn = DriverManager.getConnection(url, username, password);
16.4.3 创建语句对象
通过Connection对象的方法创建语句对象,常用的有:
createStatement():创建Statement对象prepareStatement(String sql):创建PreparedStatement对象
// 创建Statement对象
Statement stmt = conn.createStatement();
// 或者创建PreparedStatement对象
String sql = "SELECT * FROM students WHERE age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
16.4.4 ResultSet 对象
ResultSet对象代表 SQL 查询的结果集,通过它可以获取查询到的数据。
常用方法:
next():移动到下一行,返回true表示有数据getXxx(String columnName):获取指定列名的 Xxx 类型数据getXxx(int columnIndex):获取指定列索引(从 1 开始)的 Xxx 类型数据
// 执行查询
String sql = "SELECT id, name, age FROM students";
ResultSet rs = stmt.executeQuery(sql);
// 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println("ID: " + id + ", 姓名: " + name + ", 年龄: " + age);
}
16.4.5 关闭有关对象
数据库操作完成后,需要关闭相关对象,释放资源。关闭顺序与创建顺序相反:
- 关闭
ResultSet - 关闭
Statement或PreparedStatement - 关闭
Connection
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
16.5 访问 MySQL 数据库
下面通过一个完整的示例来演示如何使用 JDBC 访问 MySQL 数据库。
16.5.1 创建数据库和表
首先,我们需要创建一个数据库和表来存储学生信息:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS studentdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE studentdb;
-- 创建学生表
CREATE TABLE IF NOT EXISTS students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT,
gender VARCHAR(10),
major VARCHAR(50)
);
-- 插入测试数据
INSERT INTO students (name, age, gender, major) VALUES
('张三', 20, '男', '计算机科学'),
('李四', 21, '女', '软件工程'),
('王五', 19, '男', '数据科学');
16.5.2 访问 MySQL 数据库
下面是一个完整的 Java 程序,演示如何连接 MySQL 数据库,并执行查询、插入、更新和删除操作:
import java.sql.*;
/**
* JDBC访问MySQL数据库示例
*/
public class MySQLDemo {
// 数据库连接信息
private static final String URL = "jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456"; // 请替换为你的密码
public static void main(String[] args) {
Connection conn = null;
try {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
System.out.println("数据库连接成功!");
// 3. 执行操作
// 查询所有学生
System.out.println("\n===== 所有学生 =====");
queryAllStudents(conn);
// 添加新学生
System.out.println("\n===== 添加新学生 =====");
addStudent(conn, "赵六", 22, "男", "人工智能");
queryAllStudents(conn);
// 更新学生信息
System.out.println("\n===== 更新学生信息 =====");
updateStudentAge(conn, 4, 23);
queryAllStudents(conn);
// 删除学生
System.out.println("\n===== 删除学生 =====");
deleteStudent(conn, 4);
queryAllStudents(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 4. 关闭连接
if (conn != null) {
try {
conn.close();
System.out.println("\n数据库连接已关闭!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 查询所有学生
*/
public static void queryAllStudents(Connection conn) throws SQLException {
Statement stmt = null;
ResultSet rs = null;
try {
// 创建Statement对象
stmt = conn.createStatement();
// 执行查询
String sql = "SELECT id, name, age, gender, major FROM students";
rs = stmt.executeQuery(sql);
// 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String gender = rs.getString("gender");
String major = rs.getString("major");
System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 性别: %s, 专业: %s%n",
id, name, age, gender, major);
}
} finally {
// 关闭资源
if (rs != null) rs.close();
if (stmt != null) stmt.close();
}
}
/**
* 添加学生
*/
public static void addStudent(Connection conn, String name, int age, String gender, String major) throws SQLException {
Statement stmt = null;
try {
stmt = conn.createStatement();
String sql = String.format(
"INSERT INTO students (name, age, gender, major) VALUES ('%s', %d, '%s', '%s')",
name, age, gender, major
);
int rows = stmt.executeUpdate(sql);
System.out.println("添加了 " + rows + " 条记录");
} finally {
if (stmt != null) stmt.close();
}
}
/**
* 更新学生年龄
*/
public static void updateStudentAge(Connection conn, int id, int newAge) throws SQLException {
Statement stmt = null;
try {
stmt = conn.createStatement();
String sql = "UPDATE students SET age = " + newAge + " WHERE id = " + id;
int rows = stmt.executeUpdate(sql);
System.out.println("更新了 " + rows + " 条记录");
} finally {
if (stmt != null) stmt.close();
}
}
/**
* 删除学生
*/
public static void deleteStudent(Connection conn, int id) throws SQLException {
Statement stmt = null;
try {
stmt = conn.createStatement();
String sql = "DELETE FROM students WHERE id = " + id;
int rows = stmt.executeUpdate(sql);
System.out.println("删除了 " + rows + " 条记录");
} finally {
if (stmt != null) stmt.close();
}
}
}
注意:运行此程序前,需要添加 MySQL JDBC 驱动。如果使用 Maven 项目,可以在 pom.xml 中添加以下依赖:
xml
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency>如果是非 Maven 项目,需要下载 mysql-connector-java.jar 并添加到项目的类路径中。
16.6 使用 PreparedStatement 对象
PreparedStatement是Statement的子接口,它可以预编译 SQL 语句,提高执行效率,并且可以防止 SQL 注入攻击,是推荐使用的方式。
16.6.1 创建 PreparedStatement 对象
通过Connection.prepareStatement()方法创建PreparedStatement对象:
String sql = "SELECT * FROM students WHERE age > ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
?是参数占位符,将在执行前设置具体的值。
16.6.2 带参数的 SQL 语句
使用PreparedStatement的setXxx()方法设置参数,然后执行 SQL 语句:
import java.sql.*;
/**
* PreparedStatement使用示例
*/
public class PreparedStatementDemo {
private static final String URL = "jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
Connection conn = null;
try {
// 加载驱动并建立连接
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 使用PreparedStatement查询年龄大于20的学生
System.out.println("===== 年龄大于20的学生 =====");
queryStudentsByAge(conn, 20);
// 使用PreparedStatement添加学生
System.out.println("\n===== 添加新学生 =====");
addStudent(conn, "孙七", 22, "女", "物联网工程");
// 使用PreparedStatement更新学生信息
System.out.println("\n===== 更新学生专业 =====");
updateStudentMajor(conn, 5, "智能科学与技术");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭连接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 查询年龄大于指定值的学生
*/
public static void queryStudentsByAge(Connection conn, int minAge) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 创建PreparedStatement对象
String sql = "SELECT id, name, age, gender, major FROM students WHERE age > ?";
pstmt = conn.prepareStatement(sql);
// 设置参数(参数索引从1开始)
pstmt.setInt(1, minAge);
// 执行查询
rs = pstmt.executeQuery();
// 处理结果
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String gender = rs.getString("gender");
String major = rs.getString("major");
System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 性别: %s, 专业: %s%n",
id, name, age, gender, major);
}
} finally {
// 关闭资源
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
}
}
/**
* 添加学生
*/
public static void addStudent(Connection conn, String name, int age, String gender, String major) throws SQLException {
PreparedStatement pstmt = null;
try {
String sql = "INSERT INTO students (name, age, gender, major) VALUES (?, ?, ?, ?)";
pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, name);
pstmt.setInt(2, age);
pstmt.setString(3, gender);
pstmt.setString(4, major);
// 执行更新
int rows = pstmt.executeUpdate();
System.out.println("添加了 " + rows + " 条记录");
} finally {
if (pstmt != null) pstmt.close();
}
}
/**
* 更新学生专业
*/
public static void updateStudentMajor(Connection conn, int id, String newMajor) throws SQLException {
PreparedStatement pstmt = null;
try {
String sql = "UPDATE students SET major = ? WHERE id = ?";
pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, newMajor);
pstmt.setInt(2, id);
// 执行更新
int rows = pstmt.executeUpdate();
System.out.println("更新了 " + rows + " 条记录");
} finally {
if (pstmt != null) pstmt.close();
}
}
}
PreparedStatement与Statement相比有以下优势:
- 性能更好:预编译的 SQL 语句可以重复执行,提高效率
- 更安全:可以防止 SQL 注入攻击
- 代码更清晰:将 SQL 语句与参数分离,便于维护
16.7 DAO 设计模式
DAO(Data Access Object,数据访问对象)设计模式用于将数据访问逻辑与业务逻辑分离,提供一种统一的方式来访问数据。
DAO 模式的主要组件:
- 实体类(Entity/Model):对应数据库中的表,封装数据
- DAO 接口:定义数据访问的方法
- DAO 实现类:实现 DAO 接口,具体处理数据库操作
- DAO 工厂类:创建 DAO 实例
下面是一个使用 DAO 模式操作学生信息的完整示例:
- 学生实体类
/**
* 学生实体类
*/
public class Student {
private int id;
private String name;
private int age;
private String gender;
private String major;
// 构造方法
public Student() {}
public Student(String name, int age, String gender, String major) {
this.name = name;
this.age = age;
this.gender = gender;
this.major = major;
}
public Student(int id, String name, int age, String gender, String major) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.major = major;
}
// getter和setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "', age=" + age +
", gender='" + gender + "', major='" + major + "'}";
}
}
2.学生 DAO 接口
import java.util.List;
/**
* 学生DAO接口
*/
public interface StudentDAO {
// 添加学生
void addStudent(Student student) throws SQLException;
// 根据ID查询学生
Student getStudentById(int id) throws SQLException;
// 查询所有学生
List<Student> getAllStudents() throws SQLException;
// 更新学生信息
void updateStudent(Student student) throws SQLException;
// 删除学生
void deleteStudent(int id) throws SQLException;
}
3.学生 DAO 实现类
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 学生DAO实现类
*/
public class StudentDAOImpl implements StudentDAO {
private Connection conn;
// 构造方法,接收数据库连接
public StudentDAOImpl(Connection conn) {
this.conn = conn;
}
@Override
public void addStudent(Student student) throws SQLException {
PreparedStatement pstmt = null;
try {
String sql = "INSERT INTO students (name, age, gender, major) VALUES (?, ?, ?, ?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, student.getName());
pstmt.setInt(2, student.getAge());
pstmt.setString(3, student.getGender());
pstmt.setString(4, student.getMajor());
pstmt.executeUpdate();
} finally {
if (pstmt != null) pstmt.close();
}
}
@Override
public Student getStudentById(int id) throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
String sql = "SELECT id, name, age, gender, major FROM students WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
return new Student(
rs.getInt("id"),
rs.getString("name"),
rs.getInt("age"),
rs.getString("gender"),
rs.getString("major")
);
}
return null;
} finally {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
}
}
@Override
public List<Student> getAllStudents() throws SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
String sql = "SELECT id, name, age, gender, major FROM students";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
List<Student> students = new ArrayList<>();
while (rs.next()) {
Student student = new Student(
rs.getInt("id"),
rs.getString("name"),
rs.getInt("age"),
rs.getString("gender"),
rs.getString("major")
);
students.add(student);
}
return students;
} finally {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
}
}
@Override
public void updateStudent(Student student) throws SQLException {
PreparedStatement pstmt = null;
try {
String sql = "UPDATE students SET name = ?, age = ?, gender = ?, major = ? WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, student.getName());
pstmt.setInt(2, student.getAge());
pstmt.setString(3, student.getGender());
pstmt.setString(4, student.getMajor());
pstmt.setInt(5, student.getId());
pstmt.executeUpdate();
} finally {
if (pstmt != null) pstmt.close();
}
}
@Override
public void deleteStudent(int id) throws SQLException {
PreparedStatement pstmt = null;
try {
String sql = "DELETE FROM students WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
pstmt.executeUpdate();
} finally {
if (pstmt != null) pstmt.close();
}
}
}
4.DAO 工厂类
import java.sql.Connection;
/**
* DAO工厂类
*/
public class DAOFactory {
// 获取StudentDAO实例
public static StudentDAO getStudentDAO(Connection conn) {
return new StudentDAOImpl(conn);
}
}
5.测试类
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
/**
* DAO模式测试类
*/
public class DAODemo {
private static final String URL = "jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
Connection conn = null;
try {
// 加载驱动并建立连接
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 获取StudentDAO实例
StudentDAO studentDAO = DAOFactory.getStudentDAO(conn);
// 添加学生
System.out.println("===== 添加学生 =====");
Student newStudent = new Student("周八", 21, "男", "自动化");
studentDAO.addStudent(newStudent);
System.out.println("添加学生成功");
// 查询所有学生
System.out.println("\n===== 所有学生 =====");
List<Student> students = studentDAO.getAllStudents();
for (Student s : students) {
System.out.println(s);
}
// 根据ID查询学生
System.out.println("\n===== 查询ID为1的学生 =====");
Student student = studentDAO.getStudentById(1);
System.out.println(student);
// 更新学生信息
System.out.println("\n===== 更新学生信息 =====");
if (student != null) {
student.setAge(22);
studentDAO.updateStudent(student);
System.out.println("更新后:" + studentDAO.getStudentById(1));
}
// 删除学生
System.out.println("\n===== 删除最后一个学生 =====");
if (!students.isEmpty()) {
int lastId = students.get(students.size() - 1).getId();
studentDAO.deleteStudent(lastId);
System.out.println("删除后所有学生:");
for (Student s : studentDAO.getAllStudents()) {
System.out.println(s);
}
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭连接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
DAO 设计模式的优点:
- 分离数据访问逻辑和业务逻辑,降低耦合度
- 便于维护和扩展,如果数据库类型改变,只需修改 DAO 实现类
- 提高代码复用性
16.8 可滚动和可更新的 ResultSet
默认情况下,ResultSet只能向前移动,并且是只读的。但通过设置Statement的参数,可以创建可滚动和可更新的ResultSet。
16.8.1 可滚动的 ResultSet
可滚动的ResultSet允许在结果集中自由移动,可以向前、向后,甚至直接跳到特定行。
创建可滚动的Statement需要指定两个参数:
- 结果集类型:
ResultSet.TYPE_SCROLL_INSENSITIVE或ResultSet.TYPE_SCROLL_SENSITIVE - 结果集并发性:
ResultSet.CONCUR_READ_ONLY(默认,只读)
import java.sql.*;
/**
* 可滚动的ResultSet示例
*/
public class ScrollableResultSetDemo {
private static final String URL = "jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载驱动并建立连接
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 创建可滚动的Statement
// 参数1:ResultSet.TYPE_SCROLL_INSENSITIVE 表示可滚动且对数据库的更改不敏感
// 参数2:ResultSet.CONCUR_READ_ONLY 表示只读
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
// 执行查询
String sql = "SELECT id, name, age FROM students";
rs = stmt.executeQuery(sql);
System.out.println("===== 正向遍历 =====");
while (rs.next()) {
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
}
System.out.println("\n===== 反向遍历 =====");
while (rs.previous()) {
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
}
System.out.println("\n===== 移动到第三行 =====");
rs.absolute(3); // 移动到绝对位置
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
System.out.println("\n===== 移动到最后一行 =====");
rs.last();
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
System.out.println("\n===== 移动到第一行 =====");
rs.first();
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"));
System.out.println("\n===== 当前行号 =====");
System.out.println("当前行号: " + rs.getRow());
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
ResultSet的常用移动方法:
next():移动到下一行previous():移动到上一行first():移动到第一行last():移动到最后一行absolute(int row):移动到指定行(正数从开头计数,负数从结尾计数)relative(int rows):相对当前位置移动指定行数beforeFirst():移动到第一行之前afterLast():移动到最后一行之后getRow():获取当前行号
16.8.2 可更新的 ResultSet
可更新的ResultSet允许直接通过结果集修改数据库中的数据。
创建可更新的Statement需要指定两个参数:
- 结果集类型:
ResultSet.TYPE_SCROLL_INSENSITIVE - 结果集并发性:
ResultSet.CONCUR_UPDATABLE
import java.sql.*;
/**
* 可更新的ResultSet示例
*/
public class UpdatableResultSetDemo {
private static final String URL = "jdbc:mysql://localhost:3306/studentdb?useSSL=false&serverTimezone=UTC";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载驱动并建立连接
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 创建可更新的Statement
// 参数1:ResultSet.TYPE_SCROLL_INSENSITIVE 表示可滚动
// 参数2:ResultSet.CONCUR_UPDATABLE 表示可更新
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
// 查询数据
String sql = "SELECT id, name, age, major FROM students";
rs = stmt.executeQuery(sql);
System.out.println("===== 更新前 =====");
while (rs.next()) {
System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 专业: %s%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"), rs.getString("major"));
}
// 更新数据
System.out.println("\n===== 执行更新 =====");
// 更新特定行
rs.absolute(2); // 移动到第二行
rs.updateInt("age", 23); // 更新年龄
rs.updateRow(); // 提交更新
// 插入新行
rs.moveToInsertRow(); // 移动到插入行
rs.updateString("name", "吴九");
rs.updateInt("age", 20);
rs.updateString("gender", "男");
rs.updateString("major", "电子信息工程");
rs.insertRow(); // 插入新行
rs.moveToCurrentRow(); // 移动回原来的位置
// 删除行
rs.last(); // 移动到最后一行
int deleteId = rs.getInt("id");
rs.deleteRow(); // 删除当前行
System.out.println("删除了ID为 " + deleteId + " 的学生");
// 显示更新后的数据
System.out.println("\n===== 更新后 =====");
rs.beforeFirst(); // 移动到结果集开头
while (rs.next()) {
System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 专业: %s%n",
rs.getInt("id"), rs.getString("name"), rs.getInt("age"), rs.getString("major"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
}
使用可更新的ResultSet修改数据的方法:
-
更新现有行:
- 移动到要更新的行
- 使用
updateXxx()方法修改列值 - 调用
updateRow()提交更新
-
插入新行:
- 调用
moveToInsertRow()移动到插入行 - 使用
updateXxx()方法设置新行的列值 - 调用
insertRow()插入新行 - 调用
moveToCurrentRow()移动回原来的位置
- 调用
-
删除行:
- 移动到要删除的行
- 调用
deleteRow()删除行
注意:不是所有的查询都可以生成可更新的
ResultSet,通常需要满足以下条件:
- 查询的是单个表
- 查询中包含表的主键
- 没有使用
SELECT *(虽然 MySQL 允许,但不推荐)
16.9 小结
本章主要介绍了 JDBC 数据库编程的相关知识,包括:
- 数据库系统基础知识,包括关系数据库和 SQL 语言
- MySQL 数据库的安装、配置和基本操作
- JDBC 体系结构和核心 API
- 使用 JDBC 访问数据库的基本步骤:加载驱动、建立连接、创建语句、执行 SQL、处理结果、关闭资源
PreparedStatement的使用,它比Statement更安全、高效- DAO 设计模式,用于分离数据访问逻辑和业务逻辑
- 可滚动和可更新的
ResultSet的使用
掌握 JDBC 数据库编程是 Java 开发的重要技能,它使 Java 应用程序能够与各种关系型数据库进行交互,实现数据的存储、查询、更新和删除等操作。
编程练习
-
练习 1:学生成绩管理系统
设计一个学生成绩管理系统,实现以下功能:
- 添加学生信息(学号、姓名、性别、年龄)
- 添加学生成绩(学号、课程名、成绩)
- 查询指定学生的所有成绩
- 查询指定课程的所有学生成绩
- 更新学生成绩
- 删除学生信息及其成绩
要求:使用 DAO 设计模式,使用
PreparedStatement防止 SQL 注入。 -
练习 2:图书管理系统
设计一个简单的图书管理系统,实现以下功能:
- 添加图书(图书编号、书名、作者、出版社、出版日期、库存数量)
- 借阅图书(记录借阅人、图书编号、借阅日期、应还日期)
- 归还图书(更新归还日期和库存)
- 查询图书信息
- 查询图书借阅记录
要求:使用可更新的
ResultSet实现部分功能,使用事务保证数据一致性。

以上练习的参考答案可以通过实际开发来完成,建议按照本章介绍的 DAO 模式进行设计,将数据访问逻辑与业务逻辑分离,提高代码的可维护性和可扩展性。
希望本章的内容能帮助你掌握 JDBC 数据库编程的核心技能,为后续的 Java 开发打下坚实的基础。如果有任何疑问或建议,欢迎在评论区留言讨论!
更多推荐




所有评论(0)