李校长站在教务处门口,看着手里那张皱巴巴的课表,叹了口气。这是本周第三次出现课程冲突了——高二三班的物理课被排进了音乐教室,而隔壁班的美术课却找不到老师。这种“手工协调”的工作方式,在这个数字化时代显得格格不入。
回到办公室,他打开电脑,看着屏幕上简陋的Excel表格,脑海里浮现出一个念头:能不能有一套系统,让排课、选课、查分这一切都自动运转起来?
于是,一个基于JSP的Java Web教育管理系统项目,就这样在办公室里悄然孕育。
一、为什么选择JSP?一个老派但稳健的选择
你可能会问:都2024年了,为什么还要用JSP?Spring Boot、Vue、React不是更香吗?
说实话,如果这是一个全新的大规模商业项目,我可能会推荐更现代的架构。但在教育信息化这个特定领域,JSP有几个不可替代的优势:
- 零部署成本:学校服务器往往是老旧的Windows Server或Linux,Tomcat一装就能跑,不需要复杂的构建工具链。
- 技术门槛低:学校的IT维护人员可能只会Java和一点HTML,JSP的“脚本式”开发对他们最友好。
- 单文件架构:JSP文件本身就是页面,修改即时生效,调试直观,对于快速迭代的小型管理系统非常高效。
- 与Java生态无缝集成:学校的现有系统(如学籍管理、财务系统)大多是Java写的,JSP可以直接调用已有的DAO层和业务逻辑。
当然,JSP也有缺点——视图和逻辑耦合、难以维护大型项目、前端体验受限。但在一个中小型学校、预算有限、需求明确的场景下,它是一个务实且高效的选择。
二、系统架构:三层结构的经典实践
我们的教育管理系统采用经典的MVC三层架构,这是Java Web开发的“老规矩”,但至今依然有效。
2.1 架构分层示意
┌─────────────────────────────────────────────────────────┐
│ 表现层 (View) │
│ index.jsp / course.jsp / score.jsp / admin.jsp │
│ - JSP + JSTL + 少量JavaScript │
│ - 负责数据展示和用户交互 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 控制层 (Controller) │
│ CourseServlet / ScoreServlet / StudentServlet │
│ - 继承HttpServlet,处理HTTP请求 │
│ - 调用Service层,转发到对应JSP │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 业务层 (Service) │
│ CourseService / ScoreService / StudentService │
│ - 纯Java类,封装业务逻辑 │
│ - 事务管理、数据校验、业务规则判断 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 数据访问层 (DAO) │
│ CourseDAO / ScoreDAO / StudentDAO │
│ - JDBC原生操作或MyBatis(本例用原生JDBC演示) │
│ - 增删改查,封装SQL语句 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 数据层 (Database) │
│ MySQL 8.0 / 课程表 / 成绩表 / 学生表 │
└─────────────────────────────────────────────────────────┘
2.2 为什么不用Spring?
在真实项目中,你可能想用Spring MVC或Spring Boot。但为了教学清晰和部署简单,我们直接使用Servlet + JSP。这样你可以看到一个请求从浏览器到数据库的完整生命周期,而不是被框架“黑盒”包裹。
三、数据库设计:四个核心表撑起整个系统
在开始写代码之前,我们需要明确数据长什么样。一个教育管理系统,最核心的实体就是:学生、教师、课程、成绩。
3.1 ER关系图(文字描述)
- 学生 (Student) 与 课程 (Course):多对多(一个学生选多门课,一门课有多个学生)
- 教师 (Teacher) 与 课程 (Course):一对多(一个教师教多门课,一门课只有一个主讲教师)
- 成绩 (Score) 是学生与课程的关联表,记录每个学生在每门课的成绩
3.2 SQL建表语句
-- 创建数据库
CREATE DATABASE IF NOT EXISTS school_management DEFAULT CHARACTER SET utf8mb4;
USE school_management;
-- 学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
student_no VARCHAR(20) UNIQUE NOT NULL, -- 学号
name VARCHAR(50) NOT NULL, -- 姓名
gender ENUM('男', '女') DEFAULT '男',
class_name VARCHAR(50), -- 班级
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 教师表
CREATE TABLE teacher (
id INT PRIMARY KEY AUTO_INCREMENT,
teacher_no VARCHAR(20) UNIQUE NOT NULL, -- 工号
name VARCHAR(50) NOT NULL,
department VARCHAR(50), -- 所属院系
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 课程表
CREATE TABLE course (
id INT PRIMARY KEY AUTO_INCREMENT,
course_code VARCHAR(20) UNIQUE NOT NULL, -- 课程代码
course_name VARCHAR(100) NOT NULL, -- 课程名称
credit DECIMAL(3,1) NOT NULL, -- 学分
teacher_id INT,
schedule VARCHAR(100), -- 上课时间(如:周一 3-4节)
classroom VARCHAR(50), -- 教室
semester VARCHAR(20), -- 学期(如:2024-2025-1)
FOREIGN KEY (teacher_id) REFERENCES teacher(id)
);
-- 成绩表(学生选课关联)
CREATE TABLE score (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
score DECIMAL(5,2), -- 分数(0-100)
grade VARCHAR(5), -- 等级(A/B/C/D/F)
exam_date DATE, -- 考试日期
UNIQUE KEY unique_student_course (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
-- 插入测试数据
INSERT INTO teacher (teacher_no, name, department) VALUES
('T001', '张伟', '数学系'),
('T002', '李娜', '物理系'),
('T003', '王芳', '英语系');
INSERT INTO student (student_no, name, gender, class_name) VALUES
('S001', '陈明', '男', '高三1班'),
('S002', '刘芳', '女', '高三1班'),
('S003', '赵强', '男', '高三2班');
INSERT INTO course (course_code, course_name, credit, teacher_id, schedule, classroom, semester) VALUES
('MATH101', '高等数学', 4.0, 1, '周一 1-2节', 'A-101', '2024-2025-1'),
('PHY101', '大学物理', 3.5, 2, '周二 3-4节', 'B-202', '2024-2025-1'),
('ENG101', '大学英语', 2.0, 3, '周三 5-6节', 'C-303', '2024-2025-1');
INSERT INTO score (student_id, course_id, score, grade, exam_date) VALUES
(1, 1, 85.5, 'B', '2025-01-15'),
(1, 2, 92.0, 'A', '2025-01-16'),
(2, 1, 78.0, 'C', '2025-01-15'),
(2, 3, 88.5, 'B', '2025-01-17'),
(3, 2, 95.0, 'A', '2025-01-16');
小贴士:实际项目中,建议使用MyBatis或JPA来替代原生JDBC,减少样板代码。但在这里,我们用原生JDBC,让你看清每一步发生了什么。
四、核心功能实现:从课程表到成绩查询
4.1 工具类:数据库连接(JDBCUtil)
package com.school.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCUtil {
private static final String URL = "jdbc:mysql://localhost:3306/school_management?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASS = "your_password";
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
try {
return DriverManager.getConnection(URL, USER, PASS);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public static void close(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4.2 DAO层:课程数据访问(CourseDAO)
package com.school.dao;
import com.school.util.JDBCUtil;
import java.sql.*;
import java.util.*;
public class CourseDAO {
// 查询所有课程
public List<Map<String, Object>> findAll() {
List<Map<String, Object>> list = new ArrayList<>();
String sql = "SELECT c.*, t.name as teacher_name FROM course c " +
"LEFT JOIN teacher t ON c.teacher_id = t.id ORDER BY c.semester DESC, c.course_code";
try (Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Map<String, Object> map = new HashMap<>();
map.put("id", rs.getInt("id"));
map.put("course_code", rs.getString("course_code"));
map.put("course_name", rs.getString("course_name"));
map.put("credit", rs.getDouble("credit"));
map.put("teacher_name", rs.getString("teacher_name"));
map.put("schedule", rs.getString("schedule"));
map.put("classroom", rs.getString("classroom"));
map.put("semester", rs.getString("semester"));
list.add(map);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
// 按学期查询课程
public List<Map<String, Object>> findBySemester(String semester) {
List<Map<String, Object>> list = new ArrayList<>();
String sql = "SELECT c.*, t.name as teacher_name FROM course c " +
"LEFT JOIN teacher t ON c.teacher_id = t.id " +
"WHERE c.semester = ? ORDER BY c.course_code";
try (Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, semester);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Map<String, Object> map = new HashMap<>();
map.put("id", rs.getInt("id"));
map.put("course_code", rs.getString("course_code"));
map.put("course_name", rs.getString("course_name"));
map.put("credit", rs.getDouble("credit"));
map.put("teacher_name", rs.getString("teacher_name"));
map.put("schedule", rs.getString("schedule"));
map.put("classroom", rs.getString("classroom"));
list.add(map);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
// 添加课程
public boolean addCourse(Map<String, String> params) {
String sql = "INSERT INTO course (course_code, course_name, credit, teacher_id, schedule, classroom, semester) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
try (Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, params.get("course_code"));
ps.setString(2, params.get("course_name"));
ps.setDouble(3, Double.parseDouble(params.get("credit")));
ps.setInt(4, Integer.parseInt(params.get("teacher_id")));
ps.setString(5, params.get("schedule"));
ps.setString(6, params.get("classroom"));
ps.setString(7, params.get("semester"));
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
// 删除课程
public boolean deleteCourse(int id) {
String sql = "DELETE FROM course WHERE id = ?";
try (Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}
4.3 Servlet层:课程控制(CourseServlet)
package com.school.servlet;
import com.school.dao.CourseDAO;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@WebServlet("/courses")
public class CourseServlet extends HttpServlet {
private CourseDAO courseDAO = new CourseDAO();
// GET:显示课程列表
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String semester = req.getParameter("semester");
List<Map<String, Object>> courses;
if (semester != null && !semester.isEmpty()) {
courses = courseDAO.findBySemester(semester);
} else {
courses = courseDAO.findAll();
}
req.setAttribute("courses", courses);
req.getRequestDispatcher("/WEB-INF/views/course/list.jsp").forward(req, resp);
}
// POST:添加课程
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Map<String, String> params = new java.util.HashMap<>();
params.put("course_code", req.getParameter("course_code"));
params.put("course_name", req.getParameter("course_name"));
params.put("credit", req.getParameter("credit"));
params.put("teacher_id", req.getParameter("teacher_id"));
params.put("schedule", req.getParameter("schedule"));
params.put("classroom", req.getParameter("classroom"));
params.put("semester", req.getParameter("semester"));
boolean success = courseDAO.addCourse(params);
if (success) {
resp.sendRedirect("/courses");
} else {
req.setAttribute("error", "添加失败,请检查输入");
req.getRequestDispatcher("/WEB-INF/views/course/add.jsp").forward(req, resp);
}
}
}
4.4 成绩模块:更复杂的查询逻辑
成绩模块是整个系统的核心,涉及多表关联查询和统计计算。
”`java package com.school.dao;
import com.school.util.JDBCUtil; import java.sql.; import java.util.;
public class ScoreDAO {
// 查询学生成绩列表(关联课程和教师信息)
public List<Map<String, Object>> findByStudentId(int studentId) {
List<Map<String, Object>> list = new ArrayList<>();
String sql = "SELECT s.*, c.course_code, c.course_name, c.credit, " +
"t.name as teacher_name, c.semester " +
"FROM score s " +
"JOIN course c ON s.course_id = c.id " +
"JOIN teacher t ON c.teacher_id = t.id " +
"WHERE s.student_id = ? " +
"ORDER BY c.semester DESC, c.course_code";
try (Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, studentId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Map<String, Object> map = new HashMap<>();
map.put("course_code", rs.getString("course_code"));
map.put("course_name", rs.getString("course_name"));
map.put("credit", rs.getDouble("credit"));
map.put("score", rs.getDouble("score"));
map.put("grade", rs.getString("grade"));
map.put("teacher_name", rs.getString("teacher_name"));
map.put("semester", rs.getString("semester"));
list.add(map);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
// 计算学生平均绩点(GPA)
public double calculate
