《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;}@Overridepublic 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;}@Overridepublic 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();}}@Overridepublic 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();}}@Overridepublic 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();}}@Overridepublic 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();}}@Overridepublic 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 开发打下坚实的基础。如果有任何疑问或建议,欢迎在评论区留言讨论!