在高校教务管理领域,选课流程的效率和准确性一直是核心挑战。传统的人工登记或基于简单脚本的选课系统,常常面临数据不一致、选课冲突频发、系统高峰期响应迟缓等问题。为解决这些痛点,我们设计并实现了一套基于SSH(Struts2 + Spring + Hibernate)整合框架的“教务通”选课管理平台。该系统通过标准化的三层架构,将表现层、业务逻辑层和数据持久层清晰分离,实现了选课业务的自动化与集中化管理,显著提升了教务工作的处理效率和数据可靠性。
系统采用Struts2作为MVC框架,负责前端请求的拦截与分发,其强大的拦截器机制和OGNL表达式为数据传递和验证提供了便利。业务层由Spring框架托管,利用其控制反转(IoC)和面向切面编程(AOP)特性,实现了服务组件的高效管理和声明式事务控制,确保了如选课、退课等关键操作的事务原子性。数据持久层则基于Hibernate构建,通过对象关系映射(ORM)将Java对象与数据库表关联,简化了数据库操作,并利用HQL(Hibernate Query Language)和Criteria API应对复杂的查询场景,如课程容量实时校验、时间冲突检测等。
数据库架构设计与核心表分析
一个稳健的选课系统离不开精心设计的数据库模型。本系统数据库包含5张核心表,结构清晰,关系明确,有效支撑了复杂的业务逻辑。
1. 学生表(t_student)
学生表是系统的基础数据表,不仅存储学生的基本信息,还通过外键关联定义了其所属的院系和专业,为后续的选课权限控制和数据统计奠定了基础。
CREATE TABLE `t_student` (
`studentNumber` varchar(15) NOT NULL,
`name` varchar(15) NOT NULL,
`sex` varchar(2) NOT NULL,
`phone` varchar(15) NOT NULL,
`grade` varchar(15) NOT NULL,
`major` int(11) NOT NULL,
`academy` int(11) NOT NULL,
`password` varchar(20) NOT NULL,
`remark` varchar(50) DEFAULT NULL,
PRIMARY KEY (`studentNumber`),
KEY `major` (`major`),
KEY `academy` (`academy`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`major`) REFERENCES `t_major` (`id`),
CONSTRAINT `t_student_ibfk_2` FOREIGN KEY (`academy`) REFERENCES `t_academy` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
设计亮点分析:
- 主键设计: 使用具有业务含义的
studentNumber(学号)作为主键,而非自增ID,这在查询学生信息时更为直接高效。 - 关系规范化:
major和academy字段并未直接存储名称,而是作为外键关联到专业表(t_major)和院系表(t_academy)。这种设计避免了数据冗余,保证了数据一致性。当专业或院系名称需要更新时,只需修改对应主表的一条记录即可。 - 扩展性考虑: 预留了
remark(备注)字段,为未来可能需要的额外信息提供了扩展空间。
2. 选课记录表(t_selectedcourse)
此表是系统的核心业务表,记录了每一次选课操作的成功结果。它是连接学生与课程的桥梁,其设计直接关系到选课业务逻辑的正确性。
CREATE TABLE `t_selectedcourse` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`studentId` varchar(15) NOT NULL,
`courseId` int(11) NOT NULL,
`score` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `studentId` (`studentId`),
KEY `courseId` (`courseId`),
CONSTRAINT `t_selectedcourse_ibfk_1` FOREIGN KEY (`studentId`) REFERENCES `t_student` (`studentNumber`),
CONSTRAINT `t_selectedcourse_ibfk_2` FOREIGN KEY (`courseId`) REFERENCES `t_course` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
设计亮点分析:
- 代理主键: 使用与业务无关的自增
id作为主键,这是一个最佳实践。它避免了使用(studentId, courseId)这类自然键作为主键可能带来的复杂性,特别是在需要关联其他表时更为灵活。 - 成绩管理:
score字段允许为NULL,这精确地反映了业务现实:选课成功初期,成绩是未知的。只有当教师录入成绩后,该字段才被更新。这种设计避免了使用特殊值(如-1)来表示未评分状态,更加规范。 - 唯一性约束: 虽然没有在DDL中显式创建唯一索引,但在应用层通过业务逻辑保证了
(studentId, courseId)组合的唯一性,防止同一学生重复选择同一门课程。
核心功能实现与代码剖析
1. 学生选课功能
选课功能是系统最核心的交互之一,涉及复杂的业务规则校验,如课程容量、时间冲突、预修课程等。其后端处理逻辑集中在Spring管理的Service组件中。

上图为学生进行选课操作的界面,系统会列出所有可选课程,并清晰展示课程容量、已选人数等关键信息。
CourseService.java (业务逻辑层)
@Service("courseService")
@Transactional
public class CourseService {
@Autowired
private CourseDAO courseDAO;
@Autowired
private SelectedCourseDAO selectedCourseDAO;
/**
* 执行选课操作,包含完整的业务规则校验
* @param studentNumber 学号
* @param courseId 课程ID
* @return 操作结果信息
*/
public String selectCourse(String studentNumber, Integer courseId) {
// 1. 检查课程是否存在且可选
Course course = courseDAO.get(Course.class, courseId);
if (course == null) {
return "课程不存在!";
}
if (course.getSelectedNum() >= course.getMaxNum()) {
return "课程容量已满,选课失败!";
}
// 2. 检查是否已选过该课程
String hql = "FROM SelectedCourse sc WHERE sc.studentId=? AND sc.courseId=?";
List<SelectedCourse> list = selectedCourseDAO.find(hql, new Object[]{studentNumber, courseId});
if (list != null && list.size() > 0) {
return "您已经选择过该课程!";
}
// 3. 检查时间冲突(简化示例,实际需比较上课时间节次)
// ... 时间冲突检测逻辑 ...
// 4. 所有校验通过,执行选课
SelectedCourse sc = new SelectedCourse();
sc.setStudentId(studentNumber);
sc.setCourseId(courseId);
// 成绩初始为null
sc.setScore(null);
selectedCourseDAO.save(sc);
// 5. 更新课程的已选人数
course.setSelectedNum(course.getSelectedNum() + 1);
courseDAO.update(course);
return "选课成功!";
}
}
该Service方法通过@Transactional注解被声明为一个事务。这意味着,只要在方法执行过程中抛出任何运行时异常,整个操作(包括插入选课记录和更新课程人数)都会回滚,从而保证了数据的一致性。
2. 选课信息查询与展示
学生需要便捷地查询自己已选的课程列表及相关信息。这通过Hibernate的HQL查询实现,并将数据传递到前端JSP页面进行渲染。

SelectedCourseAction.java (表现层控制器)
public class SelectedCourseAction extends ActionSupport {
private List<SelectedCourse> selectedCourseList;
private String studentNumber; // 从Session中获取
// Struts2 Action的默认执行方法
public String execute() {
// 构建HQL,联表查询以获取课程详情而非仅ID
String hql = "SELECT new map(sc.id as id, c.name as courseName, c.teacher as teacher, "
+ "c.credit as credit, sc.score as score) "
+ "FROM SelectedCourse sc, Course c "
+ "WHERE sc.courseId = c.id AND sc.studentId = ?";
selectedCourseList = selectedCourseDAO.find(hql, new Object[]{studentNumber});
return SUCCESS;
}
// Getter and Setter...
}
此Action使用了HQL的投影查询,通过new map语法构造了一个包含课程详细信息的Map对象列表,避免了在JSP页面上进行额外的数据库查询,提升了性能。
3. 课程管理功能
教务管理员拥有对课程信息的全生命周期管理权限,包括增、删、改、查。后端通过Hibernate的getHibernateTemplate()提供的基础CRUD方法进行操作。

上图为管理员进行课程管理的界面,支持对课程信息的全面维护。
CourseDAO.java (数据访问对象)
@Repository("courseDAO")
public class CourseDAO extends BaseDAO<Course> {
// 继承的BaseDAO已包含save, update, delete, get等方法
// 此处可编写复杂的自定义查询
/**
* 根据课程名称模糊查询
* @param name 课程名称关键词
* @return 课程列表
*/
public List<Course> findCourseByName(String name) {
String hql = "FROM Course WHERE name LIKE ?";
return this.getHibernateTemplate().find(hql, "%" + name + "%");
}
/**
* 分页查询课程列表
* @param page 当前页码
* @param pageSize 每页记录数
* @return 分页数据
*/
public PageBean<Course> findCourseByPage(int page, int pageSize) {
// 查询总记录数
String countHql = "SELECT count(*) FROM Course";
List<Long> list = this.getHibernateTemplate().find(countHql);
Long totalCount = list.get(0);
// 查询当前页数据
String dataHql = "FROM Course";
List<Course> dataList = this.getHibernateTemplate().execute(new HibernateCallback<List<Course>>() {
@Override
public List<Course> doInHibernate(Session session) throws HibernateException {
Query query = session.createQuery(dataHql);
query.setFirstResult((page - 1) * pageSize);
query.setMaxResults(pageSize);
return query.list();
}
});
return new PageBean<>(page, pageSize, totalCount.intValue(), dataList);
}
}
BaseDAO封装了Hibernate的通用操作,遵循DRY(Don't Repeat Yourself)原则,减少了代码重复。
4. 登录认证与权限控制
系统根据用户角色(学生/管理员)提供不同的功能视图。登录认证过程涉及密码验证和Session管理。

LoginAction.java
public class LoginAction extends ActionSupport {
private String username;
private String password;
private String userType; // "student" or "admin"
public String execute() {
if ("student".equals(userType)) {
Student student = studentDAO.get(Student.class, username);
if (student != null && password.equals(student.getPassword())) {
// 登录成功,将用户信息存入Session
ActionContext.getContext().getSession().put("currentUser", student);
ActionContext.getContext().getSession().put("userType", "student");
return "student_success";
}
} else if ("admin".equals(userType)) {
// 管理员验证逻辑类似...
}
this.addActionError("用户名或密码错误!");
return INPUT;
}
// ... Getter and Setter
}
通过将用户信息和类型存入Session,后续的拦截器或页面标签可以根据userType来控制功能的可见性与可访问性。
实体模型与ORM映射
Hibernate的核心在于将数据库表映射为Java实体类。以下以Course实体为例,展示其与t_course表的映射关系。
Course.java (实体类)
@Entity
@Table(name = "t_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; // 课程ID
@Column(name = "name", length = 50, nullable = false)
private String name; // 课程名称
@Column(name = "teacher", length = 20, nullable = false)
private String teacher; // 任课教师
@Column(name = "maxNum", nullable = false)
private Integer maxNum; // 最大容量
@Column(name = "selectedNum", nullable = false)
private Integer selectedNum = 0; // 已选人数,默认0
@Column(name = "credit", nullable = false)
private Integer credit; // 学分
@Column(name = "courseTime", length = 50)
private String courseTime; // 上课时间
// 无参构造器、全参构造器、Getter和Setter方法...
}
JPA注解(@Entity, @Table, @Id, @Column)清晰地定义了对象-关系的映射规则,使得对Course对象的操作可以直接转化为对t_course表的SQL操作。
功能展望与系统优化方向
尽管当前系统已稳定实现了核心选课功能,但在高性能、高并发和用户体验方面仍有持续的优化空间。
引入Redis缓存层: 对于课程列表、学生已选课程等读多写少的数据,可以引入Redis作为缓存。例如,在选课高峰期,将课程容量信息缓存至Redis,通过原子操作(如
DECR)来快速判断和更新剩余名额,极大减轻数据库压力。// 伪代码示例 @Service public class CourseServiceWithCache { @Autowired private RedisTemplate redisTemplate; public boolean selectCourseWithCache(String studentId, Integer courseId) { String key = "course:stock:" + courseId; Long remaining = redisTemplate.opsForValue().decrement(key); if (remaining >= 0) { // 缓存中扣减成功,再进行数据库持久化操作 // ... 异步或同步写入数据库 return true; } else { // 库存不足,回滚缓存 redisTemplate.opsForValue().increment(key); return false; } } }前后端分离架构重构: 将现有的JSP技术栈重构为前后端分离模式。后端SSH框架演变为纯RESTful API服务(可使用SpringBoot简化配置),前端采用Vue.js或React等现代化框架。这将使前端交互更加流畅,并支持移动端App的开发。
细粒度权限控制与审计日志: 引入Spring Security或Apache Shiro框架,实现基于角色的访问控制(RBAC)甚至基于权限点的更细粒度控制。同时,建立完整的操作日志系统,记录所有关键操作(如登录、选课、退课、成绩修改),便于问题追溯和安全审计。
选课策略引擎: 实现更复杂的选课规则,如权重抽签、分年级分批次选课、预修课程强制校验等。可以设计一个可配置的规则引擎,使教务人员无需修改代码即可调整选课策略,增强系统的灵活性和适应性。
系统监控与性能分析: 集成监控工具(如Spring Boot Actuator、Prometheus),对系统的QPS、响应时间、异常率等关键指标进行实时监控。同时,使用Druid等连接池的监控功能,分析SQL性能,对慢查询进行优化,保障系统长期稳定运行。
该系统通过经典的SSH框架整合,成功构建了一个结构清晰、功能完备的高校选课管理解决方案。其严谨的数据库设计、分层的代码架构以及事务性的业务处理,为高校教务信息化提供了坚实的技术基础。面向未来,通过引入缓存、微服务、前端框架等新技术,该系统具备持续演进为更强大、更智能的教务管理平台的巨大潜力。