一、实验背景
本次实验要设计一个学生选课信息分页查询系统,实现数据库记录的分页显示功能。
功能要求:
- 建立MySQL数据库,创建学生选课信息表(至少30条记录)
- 基于SpringMVC+Hibernate框架开发
- 实现分页查询,每页显示6条记录
- 提供首页、上一页、页码、下一页、末页导航
技术栈: SpringMVC + Hibernate + MySQL + JSP + JSTL
二、项目结构
CourseSelectDemo2/
├── src/main/java/
│ └── com.course/
│ ├── controller/
│ │ └── CourseController.java
│ ├── dao/
│ │ ├── CourseDao.java
│ │ └── CourseDaoImpl.java
│ ├── entity/
│ │ └── Course.java
│ ├── service/
│ │ ├── CourseService.java
│ │ └── CourseServiceImpl.java
│ └── util/
│ └── Page.java
├── src/main/resources/
│ └── applicationContext.xml
└── src/main/webapp/
├── index.jsp
└── WEB-INF/
├── lib/
│ └── (jar包)
├── views/
│ └── courseList.jsp
├── spring-servlet.xml
└── web.xml
三、数据库设计
3.1 创建数据库
CREATE DATABASE course_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE course_db;
3.2 创建选课信息表
CREATE TABLE course_select (id INT PRIMARY KEY AUTO_INCREMENT,stu_id VARCHAR(20) NOT NULL,course_id VARCHAR(20) NOT NULL,course_name VARCHAR(50) NOT NULL,hours INT NOT NULL,teacher VARCHAR(50) NOT NULL,location VARCHAR(50) NOT NULL,year VARCHAR(10) NOT NULL,semester VARCHAR(10) NOT NULL
);
3.3 插入30条测试数据
INSERT INTO course_select (stu_id, course_id, course_name, hours, teacher, location, year, semester) VALUES
('2024001', 'CS101', 'Web编程技术', 64, '余元辉', '陆大楼101', '2024', '1'),
('2024001', 'CS102', '计算机网络', 48, '张三', '陆大楼102', '2024', '1'),
...(共30条)
四、核心代码
4.1 实体类 Course.java
package com.course.entity;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;@Entity
@Table(name = "course_select")
public class Course {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;@Column(name = "stu_id")private String stuId;@Column(name = "course_id")private String courseId;@Column(name = "course_name")private String courseName;private Integer hours;private String teacher;private String location;private String year;private String semester;// 无参构造方法public Course() {}// Getter和Setter方法public Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public String getStuId() { return stuId; }public void setStuId(String stuId) { this.stuId = stuId; }public String getCourseId() { return courseId; }public void setCourseId(String courseId) { this.courseId = courseId; }public String getCourseName() { return courseName; }public void setCourseName(String courseName) { this.courseName = courseName; }public Integer getHours() { return hours; }public void setHours(Integer hours) { this.hours = hours; }public String getTeacher() { return teacher; }public void setTeacher(String teacher) { this.teacher = teacher; }public String getLocation() { return location; }public void setLocation(String location) { this.location = location; }public String getYear() { return year; }public void setYear(String year) { this.year = year; }public String getSemester() { return semester; }public void setSemester(String semester) { this.semester = semester; }
}
代码解析:
注解 作用
@Entity 标记这是一个实体类,与数据库表映射
@Table(name = "course_select") 指定对应的数据库表名
@Id 标记主键字段
@GeneratedValue(strategy = GenerationType.IDENTITY) 主键自增
@Column(name = "stu_id") 指定对应的数据库列名
4.2 分页工具类 Page.java
package com.course.util;import java.util.List;public class Page<T> {private int pageNo; // 当前页码private int pageSize; // 每页记录数private int totalCount; // 总记录数private int totalPages; // 总页数private List<T> list; // 当前页数据public Page(int pageNo, int pageSize) {this.pageNo = pageNo;this.pageSize = pageSize;}public int getPageNo() { return pageNo; }public void setPageNo(int pageNo) { this.pageNo = pageNo; }public int getPageSize() { return pageSize; }public void setPageSize(int pageSize) { this.pageSize = pageSize; }public int getTotalCount() { return totalCount; }public void setTotalCount(int totalCount) { this.totalCount = totalCount;this.totalPages = (int) Math.ceil((double) totalCount / pageSize);}public int getTotalPages() { return totalPages; }public List<T> getList() { return list; }public void setList(List<T> list) { this.list = list; }public int getStartRow() {return (pageNo - 1) * pageSize;}public boolean isFirst() { return pageNo <= 1; }public boolean isLast() { return pageNo >= totalPages; }
}
4.3 DAO层实现(分页核心代码) CourseDaoImpl.java
package com.course.dao;import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;import com.course.entity.Course;
import com.course.util.Page;@Repository
public class CourseDaoImpl implements CourseDao {@Autowiredprivate SessionFactory sessionFactory;private Session getCurrentSession() {return sessionFactory.getCurrentSession();}@Overridepublic int getTotalCount() {String hql = "select count(*) from Course";Query query = getCurrentSession().createQuery(hql);return ((Long) query.uniqueResult()).intValue();}@Overridepublic Page<Course> findPage(int pageNo, int pageSize) {Page<Course> page = new Page<>(pageNo, pageSize);int totalCount = getTotalCount();page.setTotalCount(totalCount);if (totalCount > 0) {String hql = "from Course order by id";Query query = getCurrentSession().createQuery(hql);query.setFirstResult(page.getStartRow()); // 起始行query.setMaxResults(pageSize); // 每页条数page.setList(query.list());}return page;}
}
分页核心代码解析:
代码 作用
getTotalCount() 查询总记录数,用于计算总页数
page.setTotalCount(totalCount) 设置总记录数,自动计算总页数
query.setFirstResult(startRow) 设置查询的起始位置
query.setMaxResults(pageSize) 设置查询的最大返回条数
分页原理:
第1页:startRow = 0,取第1-6条
第2页:startRow = 6,取第7-12条
第3页:startRow = 12,取第13-18条
...
4.4 Controller层 CourseController.java
package com.course.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import com.course.entity.Course;
import com.course.service.CourseService;
import com.course.util.Page;@Controller
public class CourseController {@Autowiredprivate CourseService courseService;@RequestMapping("/courseList")public String list(@RequestParam(value = "pageNo", defaultValue = "1") int pageNo,Model model) {int pageSize = 6; // 每页6条记录Page<Course> page = courseService.findPage(pageNo, pageSize);model.addAttribute("page", page);return "courseList";}
}
4.5 JSP分页页面 courseList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>学生选课信息 - 分页显示</title>
</head>
<body><div class="container"><h1>📚 学生选课信息表</h1><div class="info">共 ${page.totalCount} 条记录,当前第 ${page.pageNo} 页 / 共 ${page.totalPages} 页</div><table><thead><tr><th>学号</th><th>课程号</th><th>课程名</th><th>学时数</th><th>任课教师</th><th>授课地点</th><th>学年</th><th>学期</th></tr></thead><tbody><c:forEach items="${page.list}" var="course"><tr><td>${course.stuId}</td><td>${course.courseId}</td><td>${course.courseName}</td><td>${course.hours}</td><td>${course.teacher}</td><td>${course.location}</td><td>${course.year}</td><td>${course.semester}</td></tr></c:forEach></tbody></table><div class="pagination"><c:if test="${!page.first}"><a href="courseList?pageNo=1">首页</a><a href="courseList?pageNo=${page.pageNo - 1}">上一页</a></c:if><c:forEach begin="1" end="${page.totalPages}" var="i"><c:if test="${i == page.pageNo}"><span class="current">${i}</span></c:if><c:if test="${i != page.pageNo}"><a href="courseList?pageNo=${i}">${i}</a></c:if></c:forEach><c:if test="${!page.last}"><a href="courseList?pageNo=${page.pageNo + 1}">下一页</a><a href="courseList?pageNo=${page.totalPages}">末页</a></c:if></div></div>
</body>
</html>
4.6 Spring配置文件 spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="..."><context:component-scan base-package="com.course.controller" /><mvc:annotation-driven /><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/" /><property name="suffix" value=".jsp" /></bean></beans>
五、运行效果
5.1 第一页效果

显示第1-6条记录,页码高亮显示"1"
5.2 第二页效果

显示第7-12条记录,页码高亮显示"2"
六、遇到的问题及解决
问题1:Hibernate Query类导入错误
现象: import org.hibernate.query.Query 报错
原因: Hibernate 4 使用的是 org.hibernate.Query,不是 org.hibernate.query.Query
解决: 改为 import org.hibernate.Query
问题2:项目部署后访问404
现象: 访问 http://localhost:8080/CourseSelectDemo2/courseList 返回404
原因: 项目没有被正确发布到Tomcat的webapps目录
解决: 手动将项目复制到 webapps 目录下
问题3:Page类未定义
现象: The public type Page must be defined 报错
原因: Page.java 文件名写成了小写 page.java
解决: 重命名为 Page.java(大写)
七、技术总结
通过本次实验,掌握了:
知识点 掌握程度
SpringMVC框架配置 ✅ 熟练掌握
Hibernate与Spring整合 ✅ 熟练掌握
分页查询原理 ✅ 理解原理
JSTL标签库使用 ✅ 熟练使用
MVC设计模式 ✅ 理解应用
分页核心原理:
setFirstResult(startRow):设置查询起始位置
setMaxResults(pageSize):设置每页返回条数
总页数 = ceil(总记录数 / 每页条数)
许俊杰: 本次实验我负责项目架构搭建和分页核心代码编写。通过本次实验,我掌握了SpringMVC和Hibernate框架的整合方法,理解了分页查询的核心原理——使用setFirstResult()和setMaxResults()实现数据库层面的分页。遇到的主要问题是Hibernate版本与MySQL驱动的兼容性问题,通过将MySQL驱动升级到8.0版本解决。另一个问题是包扫描配置,通过正确配置context:component-scan解决。
叶海林: 我负责DAO层和Service层的编写。学会了使用Hibernate的Query对象执行HQL查询,以及使用@Repository、@Service、@Autowired注解管理Bean。理解了事务管理的重要性,通过@Transactional确保数据一致性。
王兴安: 我负责JSP页面设计和分页导航实现。学会了使用JSTL标签库的<c:forEach>遍历数据,以及<c:if>条件判断。通过CSS实现了美观的表格样式和分页按钮效果,提升了用户体验。
李想: 我负责数据库设计、测试数据插入和系统功能测试。创建了30条测试数据,测试了首页、中间页、末页、上一页、下一页等所有分页功能,验证了边界情况(第1页没有首页/上一页按钮,末页没有下一页/末页按钮)的正确性。
八、源码地址
在线访问:http://公网IP:8080/CourseSelectDemo2/courseList
九、参考资料
《Web编程技术》(第二版),余元辉,清华大学出版社,2024.8
Spring Framework官方文档
