Java JDBC 数据库操作实战:从连接到连接池(附学生管理系统)
一、前言
数据库操作是 Java 后端开发的必备技能,而 JDBC(Java Database Connectivity)是 Java 程序与数据库交互的基础 API。本文将以一个完整的 “简易学生管理系统” 项目为目标,带大家从最基础的 JDBC 连接开始,一步步深入到核心 API 的使用、SQL 注入的防范,最终实现连接池的优化,让你真正掌握企业级开发中的数据库操作流程。
二、JDBC 连接数据库 6 步

2.1 核心 API 介绍
- DriverManager:驱动管理类,用于加载数据库驱动并创建
Connection对象。 - Connection:连接对象,代表 Java 程序与数据库的一个会话。
- Statement:执行 SQL 语句的对象。
- ResultSet:结果集对象,用于存储 SQL 查询返回的数据。
2.2 连接代码示例
java
运行
import java.sql.*;
public class JdbcConnection {
private static final String URL = "jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
System.out.println("数据库连接成功!");
// 3. 创建Statement对象
stmt = conn.createStatement();
// 4. 执行SQL查询
String sql = "SELECT id, name, age FROM student";
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.printf("ID: %d, 姓名: %s, 年龄: %d%n", id, name, age);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源(注意顺序)
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(); }
}
}
}
三、SQL 注入演示与防范

3.1 什么是 SQL 注入?
SQL 注入是一种常见的网络攻击手段,黑客通过在用户输入的数据中插入 SQL 语句片段,欺骗数据库执行非授权的操作。
3.2 注入风险演示(使用 Statement)
java
运行
// 假设这是一个登录验证方法
public static boolean login(String username, String password) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean isLogin = false;
try {
conn = DriverManager.getConnection(URL, USER, PASSWORD);
stmt = conn.createStatement();
// 危险!直接拼接用户输入到SQL语句中
String sql = "SELECT * FROM user WHERE username='" + username + "' AND password='" + password + "'";
rs = stmt.executeQuery(sql);
isLogin = rs.next(); // 如果有结果,说明登录成功
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源...
}
return isLogin;
}
攻击方式: 当黑客输入用户名:' OR '1'='1 --,密码任意。 拼接后的 SQL 语句变为: SELECT * FROM user WHERE username='' OR '1'='1 --' AND password='...' 这条 SQL 会查询出所有用户记录,导致登录绕过。
3.3 使用 PreparedStatement 防范注入
PreparedStatement是Statement的子类,它支持参数化查询,可以有效防止 SQL 注入。
java
运行
public static boolean safeLogin(String username, String password) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean isLogin = false;
try {
conn = DriverManager.getConnection(URL, USER, PASSWORD);
// 使用占位符?代替直接拼接
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
pstmt = conn.prepareStatement(sql);
// 设置参数(索引从1开始)
pstmt.setString(1, username);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
isLogin = rs.next();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源...
}
return isLogin;
}
原理:PreparedStatement会将 SQL 语句的结构和数据分开处理,数据库会先编译 SQL 模板,再将用户输入的数据作为纯文本参数传入,从而避免了注入风险。
四、数据库连接池配置

4.1 为什么需要连接池?
每次通过DriverManager获取新连接,都需要与数据库进行三次握手、认证等过程,开销巨大且效率低下。连接池通过预先创建并管理一组数据库连接,让应用程序可以复用这些连接,显著提高性能。
4.2 常用连接池
- HikariCP:Spring Boot 默认推荐,以高性能著称。
- Druid:阿里巴巴开源,监控功能强大,国内使用广泛。
- C3P0:经典老牌连接池,配置简单。
4.3 Druid 连接池配置示例
1. 引入 Maven 依赖
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
2. 编写配置文件(druid.properties)
properties
# 数据库基本配置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=UTC
username=root
password=your_password
# 连接池配置
initialSize=5 # 初始化连接数
minIdle=5 # 最小空闲连接数
maxActive=20 # 最大活跃连接数
maxWait=60000 # 获取连接时的最大等待时间(毫秒)
timeBetweenEvictionRunsMillis=60000 # 多久一次检测需要关闭的空闲连接
minEvictableIdleTimeMillis=300000 # 连接在池中最小生存的时间
validationQuery=SELECT 1 # 用来检测连接是否有效的SQL
testWhileIdle=true # 建议配置为true,不影响性能,并且保证安全性
testOnBorrow=false # 申请连接时执行validationQuery检测连接是否有效,会降低性能
testOnReturn=false # 归还连接时执行validationQuery检测连接是否有效,会降低性能
poolPreparedStatements=true # 开启PSCache
maxPoolPreparedStatementPerConnectionSize=20 # 每个连接上PSCache的大小
3. 编写连接池工具类
java
运行
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class DruidUtils {
private static DataSource dataSource;
static {
try {
// 加载配置文件
Properties props = new Properties();
InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
props.load(is);
// 初始化连接池
dataSource = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
throw new RuntimeException("初始化Druid连接池失败", e);
}
}
// 获取连接
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (Exception e) {
throw new RuntimeException("获取数据库连接失败", e);
}
}
// 获取DataSource
public static DataSource getDataSource() {
return dataSource;
}
}
五、简易学生管理系统实现

5.1 系统功能设计
我们将实现一个基于控制台的学生信息管理系统,包含以下功能:
- 新增学生:向数据库中插入一条新的学生记录。
- 查询学生:根据 ID 查询单个学生信息,或查询所有学生信息。
- 更新学生:修改已有学生的信息(如姓名、年龄)。
- 删除学生:根据 ID 删除一条学生记录。
5.2 数据库表设计
sql
CREATE DATABASE IF NOT EXISTS student_db;
USE student_db;
CREATE TABLE IF NOT EXISTS student (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',
name VARCHAR(50) NOT NULL COMMENT '学生姓名',
age INT COMMENT '学生年龄',
gender CHAR(1) COMMENT '学生性别(M/F)',
major VARCHAR(100) COMMENT '所属专业',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5.3 核心代码实现(以查询和新增为例)
1. 学生实体类(Student.java)
java
运行
public class Student {
private Integer id;
private String name;
private Integer age;
private String gender;
private String major;
// 构造方法、getter和setter省略...
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", major='" + major + '\'' +
'}';
}
}
2. 数据访问对象(StudentDAO.java)
java
运行
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class StudentDAO {
// 查询所有学生
public List<Student> queryAllStudents() {
List<Student> students = new ArrayList<>();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DruidUtils.getConnection();
String sql = "SELECT id, name, age, gender, major FROM student";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
while (rs.next()) {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
student.setGender(rs.getString("gender"));
student.setMajor(rs.getString("major"));
students.add(student);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源...
}
return students;
}
// 新增学生
public int addStudent(Student student) {
Connection conn = null;
PreparedStatement pstmt = null;
int affectedRows = 0;
try {
conn = DruidUtils.getConnection();
String sql = "INSERT INTO student(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());
affectedRows = pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源...
}
return affectedRows;
}
// 更新学生、删除学生等方法类似,省略...
}
3. 控制台交互界面(Main.java)
java
运行
import java.util.List;
import java.util.Scanner;
public class Main {
private static StudentDAO studentDAO = new StudentDAO();
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
while (true) {
System.out.println("===== 简易学生管理系统 =====");
System.out.println("1. 新增学生");
System.out.println("2. 查询所有学生");
System.out.println("3. 更新学生信息");
System.out.println("4. 删除学生");
System.out.println("5. 退出系统");
System.out.print("请输入操作编号:");
int choice = scanner.nextInt();
scanner.nextLine(); // 读取换行符
switch (choice) {
case 1:
addStudent();
break;
case 2:
queryAllStudents();
break;
case 3:
// updateStudent();
break;
case 4:
// deleteStudent();
break;
case 5:
System.out.println("退出系统...");
scanner.close();
return;
default:
System.out.println("无效的操作编号,请重新输入!");
}
}
}
private static void addStudent() {
System.out.print("请输入学生姓名:");
String name = scanner.nextLine();
System.out.print("请输入学生年龄:");
int age = scanner.nextInt();
scanner.nextLine();
System.out.print("请输入学生性别(M/F):");
String gender = scanner.nextLine();
System.out.print("请输入学生专业:");
String major = scanner.nextLine();
Student student = new Student();
student.setName(name);
student.setAge(age);
student.setGender(gender);
student.setMajor(major);
int result = studentDAO.addStudent(student);
if (result > 0) {
System.out.println("新增学生成功!");
} else {
System.out.println("新增学生失败!");
}
}
private static void queryAllStudents() {
List<Student> students = studentDAO.queryAllStudents();
System.out.println("===== 学生列表 =====");
for (Student s : students) {
System.out.println(s);
}
}
// updateStudent、deleteStudent等方法类似,省略...
}
六、总结
通过本文的学习和实战,我们从 JDBC 的基础 API 入手,深入理解了数据库连接、SQL 执行、结果处理的完整流程。我们重点学习了如何使用PreparedStatement防止 SQL 注入这一关键安全问题,并掌握了在生产环境中必须使用的数据库连接池技术(以 Druid 为例)。最后,我们通过一个简易学生管理系统的设计与实现,将所有知识点融会贯通,真正做到了学以致用。
希望通过这个项目,你能对 Java 数据库操作有一个全面而深入的理解,为后续学习 ORM 框架(如 MyBatis、Hibernate)打下坚实的基础。
更多推荐
所有评论(0)