你是否还记得那个每学期开学前的下午?
那是大学里最黑暗的时刻——不是课难,而是系统难。屏幕上的进度条像蜗牛爬,点击“选课”按钮后,那个旋转的小圆圈仿佛永远不会停下来。你刷新,再刷新,最后发现心仪的体育课已经被抢光,而你的学号还卡在“处理中”的界面里。
而在系统的另一端,老师们正对着成堆的成绩单发愁。Excel 表格到处传,版本混乱,汇总错误,期末那个月,办公室里的叹息声比键盘敲击声还大。
这两个场景,看似无关,实则连着同一根神经:教育数据的流转。
今天,我们不聊虚的,就聊聊 JavaServer Pages (JSP) 这个看似老派、实则硬核的技术,是如何在 Web 1.0 到 2.0 的过渡期,成为连接学生、教师和学校数据库的桥梁。虽然它不是现在的“潮流”,但理解它,就像看懂了现代 Web 系统的“祖先”是如何工作的。
一、 为什么是 JSP?一个关于“动态页面”的故事
在 JSP 出现之前,做 Web 网站主要有两种方式:
- 静态 HTML:内容改一个字,就要重新写代码、上传服务器。
- CGI 脚本:用 C 或 Perl 写程序,每次请求都启动一个新进程,慢得像老牛拉车。
JSP 的出现,解决了这两个痛点。它的核心理念很简单:把 Java 代码嵌入到 HTML 里。
想象一下,HTML 是房子的骨架和装修,而 JSP 是让房子“活”起来的神经系统。
对于学校选课系统来说,JSP 能做什么?
- 动态内容:每个学生登录,看到的“已选课程”列表都不一样。
- 业务逻辑处理:判断这个课是否还有名额,你的学分是否超限。
- 数据库交互:实时查询 MySQL 或 Oracle 数据库。
二、 场景一:解决“选课卡顿”——前端交互与后端逻辑的分离
学生选课卡顿,通常不是因为 JSP 慢,而是因为设计不合理。早期的 JSP 项目,常犯的错误是把所有逻辑都写在页面里,导致页面加载冗长,数据库压力巨大。
我们来做一个简化版的学生选课系统,看看如何用 JSP + Servlet 模式(这是黄金搭档)来解决卡顿问题。
2.1 传统“噩梦”写法 vs 推荐写法
❌ 噩梦写法(所有代码堆在 JSP 里):
<%
// 1. 获取数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "123456");
// 2. 执行查询
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM courses");
// 3. 直接输出 HTML
out.println("<table><tr><th>课程名</th><th>剩余名额</th></tr>");
while(rs.next()) {
out.println("<tr><td>" + rs.getString("name") + "</td><td>" + rs.getInt("seats") + "</td></tr>");
}
out.println("</table>");
// 4. 处理选课提交
String courseId = request.getParameter("courseId");
// ... 又是大量重复代码
%>
问题:代码混乱、难维护、每次请求都要重新编译 JSP,性能极差。
✅ 推荐写法(MVC 模式:Model-View-Controller)
- Controller (Servlet):负责接收请求、处理业务、查数据库。
- View (JSP):只负责展示数据,不含任何 Java 逻辑代码。
- Model (Java Bean):封装数据。
2.2 实际代码演示
第一步:定义数据模型 Course.java
package com.school.model;
public class Course {
private String courseId;
private String courseName;
private int availableSeats;
// 构造函数、Getter、Setter 省略
public Course(String courseId, String courseName, int availableSeats) {
this.courseId = courseId;
this.courseName = courseName;
this.availableSeats = availableSeats;
}
public String getId() { return courseId; }
public String getName() { return courseName; }
public int getSeats() { return availableSeats; }
}
第二步:创建 Servlet 处理选课逻辑 CourseServlet.java
package com.school.controller;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;
import java.util.*;
public class CourseServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 连接数据库(实际项目中应用连接池)
List<Course> courses = new ArrayList<>();
try (Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/school", "root", "123456");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM courses WHERE status='active'")) {
while (rs.next()) {
courses.add(new Course(
rs.getString("course_id"),
rs.getString("course_name"),
rs.getInt("available_seats")
));
}
} catch (SQLException e) {
e.printStackTrace();
}
// 2. 将数据交给 JSP 展示(使用 request 作用域)
request.setAttribute("courses", courses);
request.getRequestDispatcher("/WEB-INF/views/course_list.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理选课提交
String courseId = request.getParameter("courseId");
// 调用 Service 层进行选课逻辑判断、扣减名额等
// ...
response.sendRedirect("courseList"); // 重定向,防止重复提交
}
}
第三步:干净的 JSP 页面 course_list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>课程选择</title>
<style>
body { font-family: sans-serif; padding: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
.btn-select { background-color: #4CAF50; color: white; padding: 5px 10px; border: none; cursor: pointer; }
.btn-disabled { background-color: #ccc; cursor: not-allowed; }
</style>
</head>
<body>
<h2>可选课程列表</h2>
<p>欢迎同学,当前时间:<%= new java.util.Date().toLocaleString() %></p>
<c:choose>
<c:when test="${empty courses}">
<p>暂无开放课程,请稍后再试。</p>
</c:when>
<c:otherwise>
<table>
<tr>
<th>课程 ID</th>
<th>课程名称</th>
<th>剩余名额</th>
<th>操作</th>
</tr>
<c:forEach var="course" items="${courses}">
<tr>
<td>${course.id}</td>
<td>${course.name}</td>
<td>${course.seats}</td>
<td>
<c:choose>
<c:when test="${course.seats > 0}">
<form action="courseAction" method="post" style="display:inline;">
<input type="hidden" name="courseId" value="${course.id}">
<button type="submit" class="btn-select">选课</button>
</form>
</c:when>
<c:otherwise>
<button class="btn-disabled" disabled>已满员</button>
</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</table>
</c:otherwise>
</c:choose>
</body>
</html>
2.3 为什么这样能解决“卡顿”?
- 缓存机制:Servlet 可以缓存查询结果,而 JSP 在首次编译后也会被缓存,避免每次请求都重新解析。
- 分离关注点:数据库操作在 Servlet 中完成,JSP 只负责渲染,页面加载更快。
- 使用 JSTL:通过
<c:forEach>等标签库,避免了在 JSP 里写大量<% %>脚本,代码更清晰,调试更容易。 - AJAX 结合:现代 JSP 项目常结合 JavaScript (jQuery/Axios) 实现局部刷新。比如,点击“选课”后,只更新该行的名额,而不是刷新整个页面,用户体验丝滑。
三、 场景二:教师成绩管理自动化——从 Excel 到 Web 系统的飞跃
成绩管理是另一大痛点。老师们以前用 Excel 汇总,容易出错,版本混乱。用 JSP 构建的成绩管理系统,可以实现一键录入、自动统计、安全存储。
3.1 系统核心功能需求
- 登录验证:只有授权教师才能进入。
- 批量录入:上传 Excel 或逐个输入成绩。
- 自动计算:系统自动计算平均分、绩点(GPA)、排名。
- 查询与导出:按课程、班级查询,导出 PDF/Excel。
- 数据持久化:存入数据库,防止丢失。
3.2 核心代码:成绩录入与统计
假设我们有一个 Score 模型:
package com.school.model;
public class Score {
private String studentId;
private String courseId;
private double score; // 原始分 0-100
private String grade; // 等级 A/B/C...
private double gpa; // 绩点
// 构造方法省略
public String getStudentId() { return studentId; }
public void setStudentId(String studentId) { this.studentId = studentId; }
public String getCourseId() { return courseId; }
public void setCourseId(String courseId) { this.courseId = courseId; }
public double getScore() { return score; }
public void setScore(double score) { this.score = score; }
public String getGrade() { return grade; }
public void setGrade(String grade) { this.grade = grade; }
public double getGpa() { return gpa; }
public void setGpa(double gpa) { this.gpa = gpa; }
}
3.3 后端处理:成绩计算与保存 ScoreServlet.java
package com.school.controller;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.sql.*;
import java.util.*;
public class ScoreServlet extends HttpServlet {
// 计算绩点(简单 4.0 制)
private double calculateGpa(double score) {
if (score >= 90) return 4.0;
if (score >= 80) return 3.0;
if (score >= 70) return 2.0;
if (score >= 60) return 1.0;
return 0.0;
}
// 确定等级
private String getGrade(double score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
if (score >= 60) return "D";
return "F";
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String courseId = request.getParameter("courseId");
String studentIds[] = request.getParameterValues("studentIds");
String scores[] = request.getParameterValues("scores");
if (studentIds == null || scores == null) {
response.getWriter().println("数据不完整!");
return;
}
List<Score> scoreList = new ArrayList<>();
for (int i = 0; i < studentIds.length; i++) {
double score = Double.parseDouble(scores[i]);
Score scoreObj = new Score();
scoreObj.setStudentId(studentIds[i]);
scoreObj.setCourseId(courseId);
scoreObj.setScore(score);
scoreObj.setGrade(getGrade(score));
scoreObj.setGpa(calculateGpa(score));
scoreList.add(scoreObj);
}
// 调用 Service 层保存数据到数据库
// scoreService.batchSave(courseId, scoreList);
// 将处理后的数据传回 JSP 显示结果
request.setAttribute("scoreList", scoreList);
request.setAttribute("courseId", courseId);
// 跳转到成绩确认页面
request.getRequestDispatcher("/WEB-INF/views/score_confirm.jsp").forward(request, response);
}
}
3.4 前端展示:成绩确认与统计 score_confirm.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>成绩确认</title>
<style>
.container { max-width: 800px; margin: 20px auto; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: center; }
th { background-color: #e3f2fd; }
.stats { background-color: #f9f9f9; padding: 15px; border-radius: 5px; margin-top: 20px; }
.btn-confirm { background-color: #2196F3; color: white; padding: 10px 20px; border: none; cursor: pointer; font-size: 16px; }
</style>
</head>
<body>
<div class="container">
<h2>课程 [${courseId}] 成绩录入确认</h2>
<table>
<tr>
<th>学号</th>
<th>原始分</th>
<th>等级</th>
<th>绩点</th>
</tr>
<c:forEach var="score" items="${scoreList}">
<tr>
<td>${score.studentId}</td>
<td>${score.score}</td>
<td>${score.grade}</td>
<td>${score.gpa}</td>
</tr>
</c:forEach>
</table>
<div class="stats">
<h3>统计分析</h3>
<c:set var="sum" value="0" />
<c:set var="count" value="0" />
<c:forEach var="score" items="${scoreList}">
<c:set var="sum" value="${sum + score.score}" />
<c:set var="count" value="${count + 1}" />
</c:forEach>
<p><strong>平均分:</strong> ${sum/count} 分</p>
<p><strong>提交人数:</strong> ${count} 人</p>
</div>
<button class="btn-confirm" onclick="submitToDatabase()">确认并提交</button>
<a href="scoreEntry">重新录入</a>
</div>
<script>
function submitToDatabase() {
if(confirm("确定要提交所有成绩吗?提交后将无法修改!")) {
document.getElementById('confirmForm').submit();
}
}
</script>
<form id="confirmForm" action="scoreSubmit" method="post" style="display:none;">
<input type="hidden" name="courseId" value="${courseId}">
<input type="hidden" name="scores" value="${scoreList}">
</form>
</body>
</html>
3.5 自动化带来的价值
- 准确性:系统自动计算绩点和等级,避免人工计算错误。
- 效率:老师只需录入原始分,系统完成后续所有工作。
- 可视化:前端即时显示统计结果,老师可以直观看到班级整体表现。
- 数据一致性:所有成绩存入数据库,后续查询、导出、学分计算都能直接调用,无需重复录入。
四、 JSP 在现代教育系统的“遗产”与演进
你可能会问:现在都用 Spring Boot、React、Vue 了,为什么还要讲 JSP?
因为很多学校的核心系统仍然是基于 JSP + Servlet + Spring MVC 构建的。这些系统稳定、成熟、易于维护。理解 J
