☰
Java Web综合项目实战:从数据库设计到Spring Boot分页查询完整指南
2026/10/17 16:35:46 网站建设 项目流程

第五次作业,我见过不少同学直接把这个名字原封不动写进了项目文档里,文件夹叫“第五次作业”,Git提交信息也写“第五次作业完成”,甚至答辩PPT的封面就是这四个字。其实走到这一步,大家手头基本都指向同一类东西:Java Web综合项目,常见的比如学生信息管理系统、班级信息管理、图书借阅管理。为什么第五次能卡住这么多人,我觉得有必要先讲清楚——它不是一次单纯的功能拼装,而是把前四次课的内容做了一次完整串联,从数据库到服务端再到页面,任何一个环节脱节都会让你卡在原地。这篇就把我当时完成第五次作业的完整过程拆开聊,包括需求怎么定、表怎么建、代码怎么写、常见错误怎么排查,给还没动手的人排掉几颗雷。

1. 第五次作业到底在考什么:别把它当成普通的增删改查

1.1 从前四次作业看第五次作业的定位

我旁听过几次课程,基本能摸清这种“第五次作业”的进阶逻辑。第一次是静态页面,主要练HTML结构和CSS样式;第二次是Java基础,面向对象、集合、异常这些语法层面的东西;第三次开始接触数据库,建表、SQL增删改查;第四次进入JavaWeb阶段,理解请求响应、Servlet或者Spring MVC基础。到了第五次,这些知识点全被丢到一个项目里,要求做一个从页面到数据库都能跑通的系统。

说白了,第五次作业是一次综合性实战,但它的难度不在于新知识,而在于“组合”。很多人在前四次靠抄作业或者跟着视频敲一遍就过关了,到了第五次要自己把前后端连起来,才发现原来自己连最基础的路径跳转都没吃透。我见过有同学把Servlet阶段那套代码硬往Spring Boot项目里搬,结果过滤器、拦截器、路径注解全部冲突,光是项目启动就花了两天。

所以第一个建议是:先承认这次作业是一次小型的软件工程演练,而不是一道难一点的大题。你要按照“分析需求 → 设计表 → 搭工程 → 写接口 → 做页面 → 自测”的顺序走,而不是一上来就写代码。

1.2 评分和答辩最看重什么

不少同学到了第五次才第一次经历“当着老师面讲项目”的答辩环节。据我观察,评分的人其实不太在意你用了多炫酷的技术,他们一般就看四件事。

第一件事,功能完整。增删改查必须都能跑通,页面跳转不要出现404、500这种低级错误。第二件事,界面过得去。不要求设计得多漂亮,但必须是正经的表格、表单、按钮,而不是半成品页面。第三件事,代码规范。类名、方法名、变量名要有意义,Controller、Service、Mapper分层清楚,别人能看得懂。第四件事,答辩时你能讲清楚自己的设计思路。比如为什么表要这样设计、学号为什么唯一、删除班级的时候学生怎么办。

这四件事里面,最容易拉开差距的其实是第四件。代码全是自己写的,自然讲得出来;如果项目是网上下的,被老师追着问几个“为什么”,当场就露馅。所以这篇我会很强调思路解释,不光告诉你怎么写,还告诉你为什么要这样写。

2. 动手前的设计:定好表和接口再写代码

2.1 需求清单怎么列

我知道很多人不喜欢列需求清单,觉得浪费时间,直接打开IDEA就开始建包、建类。其实第五次作业你最缺的不是写代码的时间,而是返工的精力。我强烈建议先用半小时把功能清单写出来,写到一张纸或者一个Markdown文件里都行。

以最典型的学生信息管理系统为例,我当时列出的功能是这样的:

  • 学生列表:分页展示全部学生,字段包括学号、姓名、性别、年龄、班级、手机号、邮箱
  • 新增学生:表单提交,学号不能重复
  • 编辑学生:回显原数据,可修改非唯一字段
  • 删除学生:点击删除按钮,带确认提示
  • 查询学生:按姓名模糊搜索、按班级精确筛选
  • 班级管理:维护班级表,包含新增、修改、删除
  • 用户登录(可选):简单登录过滤,作为加分项

把功能清单列出来之后,你会发现自己心里踏实很多,因为整个项目的工作量被可视化了。第五次作业翻车的一大常见原因,就是做到一半兴致来了,随手加了一个“导出Excel”或者“导入照片”的功能,结果数据库和页面越改越乱,最后连基础功能都顾不上。列清单的核心作用,是帮你自己控制边界。

2.2 数据库表怎么设计

数据库设计是第五次作业里最值得花时间的一步。很多同学喜欢把班级名称直接写进学生表,这样做的确省事,但会给后续维护埋坑。比如班级名称要改,你得把所有学生记录都更新一遍;再比如想统计某个年级有多少学生,就要写一大堆重复判断。

我的做法是拆成三张表:学生表、班级表、用户表。学生表和班级表通过班级ID关联,用户表用来做可选的登录功能。建表SQL长这样。

CREATE DATABASE IF NOT EXISTS stu_manager DEFAULT CHARACTER SET utf8mb4; USE stu_manager; CREATE TABLE t_class ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(50) NOT NULL, grade VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender TINYINT DEFAULT 1 COMMENT '1-男 0-女', age INT, class_id INT, phone VARCHAR(20), email VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_stu_class FOREIGN KEY (class_id) REFERENCES t_class(id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个细节可以展开讲。学号的唯一约束,数据库层面必须加上,这会成为最后一道防线。就算你在Java代码里忘记判断学号重复,数据库也会抛异常拦住你,而不是让脏数据混进表里。性别字段我用TINYINT存0和1,而不是直接存“男”“女”,因为存数字的兼容性和查询性能都更好,显示的时候在页面上做一次转换就行。班级外键设置了ON DELETE SET NULL,含义是删除班级时,学生的class_id会被置为NULL,不会因为删了一个班级导致学生数据被误删。这一招在答辩时被问到“删除班级后学生怎么办”会非常加分。

2.3 接口路径怎么规划

数据库定了,接下来顺手把前后端交互的接口路径规划出来。很多人用Spring Boot做项目,习惯性地直接在Controller里写@RequestMapping("/"),导致所有请求都堆在根路径上,又混乱又难排查。我当时是专门画了一个简单的接口清单,表格形式,写清楚地址、方法和用途。

接口路径请求方式功能说明
/student/listGET学生列表页,支持分页和查询参数
/student/addGET打开新增表单页
/student/savePOST提交新增学生数据
/student/edit/{id}GET打开编辑表单页,回显数据
/student/updatePOST提交修改后的学生数据
/student/delete/{id}GET删除指定学生
/class/listGET班级列表页
/class/savePOST新增班级
/class/delete/{id}GET删除班级

接口路径规划好之后,Controller里要写哪些方法、方法签名大概长什么样,心里基本就有数了。这一步还能帮你提前发现结构问题:比如某个功能你想做但想不清接口怎么设计,说明需求本身还很模糊,需要回头再捋一下。

3. 项目搭建与核心技术选型

3.1 技术栈为什么这样选

第五次作业的技术选型不需要追逐前沿,但也不能太冷门。我当时用的是Spring Boot + MyBatis-Plus + Thymeleaf + MySQL这套组合,后来指导过几个学弟也推荐这一套,因为它的好处是“教程多、坑少、爆红少”。

对比一下其他方案可能更直观。纯Servlet + JSP也能完成作业,而且看起来更贴近课程原始进度,但开发效率确实低,尤其是页面跳转和数据传递全靠手工写,容易写着写着就绕晕。前后端完全分离,也就是Spring Boot + Vue,虽然更时髦,但你要同时处理跨域、异步请求、数据校验,对第一次做综合项目的同学来说学习成本有点高。Thymeleaf这种服务端模板渲染方案,等于把Java代码和HTML页面放在同一个工程里,浏览器直接访问Controller返回的页面,页面里通过模板语法拿数据,链路短,理解起来也顺。

用一个生活化的类比:做第五次作业就像从家里骑车去学校,纯Servlet是自己蹬车,费力但每一步都在自己掌控里;前后端分离是开了一辆跑车,但你可能连车库门都打不开。Thymeleaf是电动车,拧一下电门就能走,虽然上限不高,但对这次作业来说完全够了。

3.2 创建项目与依赖配置

我建议从Spring Initializr创建工程,注意不要勾一堆用不上的东西,否则第一次启动会下载半天。基础依赖只需要Spring Web、Thymeleaf和MySQL Driver。MyBatis-Plus因为不在IDEA默认选项里,需要手动往pom.xml里加依赖。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency>

有一点要提醒:MyBatis-Plus的版本和Spring Boot的版本需要匹配。我经常见到有人用了新版Spring Boot,然后又随便拉了个老版本的MyBatis-Plus,启动直接报错。稳妥的搭配是Spring Boot 2.7.x配MyBatis-Plus 3.5.x,这套组合非常成熟。

3.3 application.yml配置:数据源和模板

配置文件是第五次作业的第一个拦路虎,尤其是数据库连接。下面是我实际使用的配置,可以直接抄,但用户名和密码换成你自己的。

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/stu_manager?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 thymeleaf: cache: false mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这里有两个配置细节特别容易踩坑。第一个是driver-class-name,MySQL 5.x用的驱动是全类名com.mysql.jdbc.Driver,MySQL 8.x则必须写成com.mysql.cj.jdbc.Driver,版本写错启动必报“Failed to load driver class”。第二个是连接串里的characterEncoding=utf8,如果不加,你往数据库里传中文时会出现乱码,而且这种乱码很隐蔽,往往到答辩前才发现。

spring.thymeleaf.cache: false这个配置也很重要。开发阶段把模板缓存关掉,修改页面后刷新浏览器就能看到最新效果;否则页面改了不生效,你还以为是浏览器缓存,白折腾半天。

3.4 包结构和启动类扫描

项目包结构我习惯这样分,清晰也好扩展:

com.example.stumanager ├── controller // Controller类 ├── service // 业务接口和实现类 ├── mapper // MyBatis-Plus接口 ├── entity // 数据库实体类 ├── config // 配置类,比如MyBatis-Plus分页插件 └── StuManagerApplication.java

这里有个小重点:Mapper接口如果散落在多个包里,需要在启动类上加上@MapperScan("com.example.stumanager.mapper")。不少同学漏了这一步,启动后报“Field studentMapper required a bean of type 'StudentMapper'”,其实就是Mapper没有被Spring扫到。加了扫描注解之后,接口上的@Mapper甚至可以不加,但为了保险我一般两个都写上。

4. 一步步完成核心功能:从表到页面的完整链路

4.1 实体类与Mapper层

项目能启动之后,就可以开始写核心代码了。第一个要写的是实体类,它对应数据库里的表结构。MyBatis-Plus里的实体类用注解标注一下表名即可。

@Data @TableName("t_student") public class Student { @TableId(type = IdType.AUTO) private Integer id; private String stuNo; private String name; private Integer gender; private Integer age; private Integer classId; private String phone; private String email; private Date createTime; }

这里要注意一个约定:Java属性用驼峰命名法,比如stuNo、classId,数据库字段用下划线命名法,比如stu_no、class_id。MyBatis-Plus默认开启了驼峰映射,所以字段能自动对上。如果你哪天发现某字段查出来是null,先检查一下是不是命名对不上。

Mapper接口这一层,MyBatis-Plus大幅省略了手写XML的工作。你只需要继承一个BaseMapper,大量常用的单表查询方法就都有了。

@Mapper public interface StudentMapper extends BaseMapper<Student> { }

BaseMapper里提供的方法足够完成增删改查基本操作。遇到需要分页、条件组合的场景,我们不用写SQL,用MyBatis-Plus封装的Wrapper条件构造器处理。

4.2 Service层为什么要单独存在

前面代码已经写得很顺了,但很多同学会在这里产生一个疑问:Controller里直接调用Mapper不行吗?为什么非要加一个Service层?

答案很简单:Controller的核心职责是接收请求、返回页面;业务判断和数据组装这种事,放在Service里才合理。举个例子,新增学生时你要先做学号重复校验,这个校验放到Controller里也可以跑,但代码会越堆越多,最后Controller里几百行,别人看着也头大。第五次作业规模不大,Service层看起来像是“过度设计”,但它体现的是分层意识,答辩时很加分。

我写Service接口和实现类大概是这样的。

public interface StudentService extends IService<Student> { Page<Student> searchStudents(Integer pageNum, Integer pageSize, String name, Integer classId); boolean addStudent(Student student); boolean updateStudent(Student student); }

实现类里,查询和新增的逻辑会用到条件构造器。新增学生时还要单独判断学号是否存在。

4.3 Controller层:把请求转到页面或Service

Controller是前后端的“接线员”。我写了几个典型的方法,覆盖列表、新增、编辑、删除四条链路。

@Controller @RequestMapping("/student") public class StudentController { @Resource private StudentService studentService; @GetMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(required = false) String name, @RequestParam(required = false) Integer classId, Model model) { Page<Student> page = studentService.searchStudents(pageNum, 10, name, classId); model.addAttribute("page", page); model.addAttribute("name", name); model.addAttribute("classId", classId); return "student/list"; } @GetMapping("/add") public String addForm(Model model) { model.addAttribute("student", new Student()); return "student/form"; } @PostMapping("/save") public String save(Student student) { studentService.addStudent(student); return "redirect:/student/list"; } @GetMapping("/edit/{id}") public String editForm(@PathVariable Integer id, Model model) { model.addAttribute("student", studentService.getById(id)); return "student/form"; } @PostMapping("/update") public String update(Student student) { studentService.updateStudent(student); return "redirect:/student/list"; } @GetMapping("/delete/{id}") public String delete(@PathVariable Integer id) { studentService.removeById(id); return "redirect:/student/list"; } }

这段代码里有个值得好好说说的细节,就是save方法最后用了redirect:/student/list,而不是直接return "student/list"。这两个从页面效果上看起来差不多,但实际行为完全不同。redirect会发送一个302重定向,浏览器地址栏会变成列表页的地址,刷新页面只是重新请求一次列表;如果直接返回模板,地址栏还是/student/save,此时你一刷新,浏览器会把上次的表单数据再提交一遍,等于新增了一模一样的数据。这种重复提交问题在作业里经常出现,处理思路就是“POST后重定向”,这是Web开发里很经典的做法。

4.4 分页与条件查询最容易翻车的一环

分页在第五次作业里几乎是必考功能。很多同学一开始不写分页,测试数据只有几条看不出问题,到了答辩前拿班里几十个同学的真实数据一导入,发现页面巨长无比,才临时回来加分页。

MyBatis-Plus的分页必须在配置类里注册分页插件,否则selectPage方法不会真的拼接LIMIT语句,查出来的还是全量数据。这一步我第一遍做的时候就漏了,页面明明调了分页方法,SQL日志里却没有LIMIT。当时还以为是版本问题,排查了半天,后来才发现是没装分页插件。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

分页插件配好之后,Service里的查询方法就可以用LambdaQueryWrapper动态组装条件。

public Page<Student> searchStudents(Integer pageNum, Integer pageSize, String name, Integer classId) { LambdaQueryWrapper<Student> wrapper = Wrappers.lambdaQuery(); wrapper.like(StringUtils.hasText(name), Student::getName, name) .eq(classId != null, Student::getClassId, classId) .orderByDesc(Student::getId); return studentMapper.selectPage(new Page<>(pageNum, pageSize), wrapper); }

这段代码的核心技巧在于条件构造器的第一个参数是boolean值。StringUtils.hasText(name)为false,也就是用户没输入姓名时,这个like条件会被自动忽略;classId != null为false时,班级筛选条件也会被忽略。这样你就不需要自己写一堆if判断来拼接查询条件了。用生活的话说,这相当于一个“智能滤网”,有值才过滤,没值就放行。

selectPage返回的Page对象里,records是当前页数据,total是总记录数,current是当前页码,pages是总页数。前端拿这些属性渲染表格和翻页按钮正好。

4.5 前端页面:表格和表单用Thymeleaf渲染

后端数据准备好了,接下来是前端页面。Thymeleaf的写法其实很接近HTML,只是在标签里多了一些th:开头的属性。我做了两个核心页面,一个是列表页,一个是表单页。

列表页的核心是th:each循环,把Student集合里的每一条记录渲染成表格的一行。

<table class="table table-bordered"> <thead> <tr> <th>学号</th> <th>姓名</th> <th>性别</th> <th>年龄</th> <th>手机号</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="stu : ${page.records}"> <td th:text="${stu.stuNo}">2024001</td> <td th:text="${stu.name}">张三</td> <td th:text="${stu.gender == 1 ? '男' : '女'}">男</td> <td th:text="${stu.age}">20</td> <td th:text="${stu.phone}">13812345678</td> <td> <a class="btn btn-sm btn-warning" th:href="@{/student/edit/{id}(id=${stu.id})}">编辑</a> <a class="btn btn-sm btn-danger" th:href="@{/student/delete/{id}(id=${stu.id})}" onclick="return confirm('确定删除该学生吗?');">删除</a> </td> </tr> </tbody> </table>

这里有两个小知识点需要敲黑板。th:href="@{/student/edit/{id}(id=${stu.id})}"这种写法叫“URL模板表达式”,括号里的id=${stu.id}会被替换到路径里,生成类似/student/edit/3的链接。如果你直接把路径写成/student/edit/+${stu.id},Thymeleaf不仅会报错,页面还容易出问题。

另外,删除确认的弹窗是HTML里的onclick事件处理的,和Thymeleaf无关。这种确认机制虽然简单,但很实用,能在作业演示时避免误删,也能让页面看着更完整。

表单页的设计上,我用一个模板同时承担新增和编辑两种场景。技巧是通过student.id是否为空来判断是哪种操作:为空走新增提交地址,不为空走更新提交地址。

<form th:action="${student.id == null} ? '/student/save' : '/student/update'" method="post"> <input type="hidden" name="id" th:value="${student.id}"> <div class="form-group"> <label>学号</label> <input type="text" class="form-control" name="stuNo" th:value="${student.stuNo}"> </div> <div class="form-group"> <label>姓名</label> <input type="text" class="form-control" name="name" th:value="${student.name}"> </div> <div class="form-group"> <label>性别</label> <select name="gender" class="form-control"> <option value="1" th:selected="${student.gender == 1}">男</option> <option value="0" th:selected="${student.gender == 0}">女</option> </select> </div> <button type="submit" class="btn btn-primary">保存</button> </form>

表单提交用的是POST,字段的name属性必须和实体类的属性名一一对应,比如stuNo、name、gender。Spring Boot收到请求后会自动把这些参数封装成Student对象,这个机制叫数据绑定。很多同学在这里栽跟头:前端传的是stu_no,后端实体类属性叫stuNo,名字对不上,保存时该字段就是null。第五次作业代码量不小,遇到这种问题很难排查,所以写表单前务必核对一下name属性。

班级管理功能的结构和学生管理几乎一样,区别只是表结构简单一些,不再需要额外的分区和搜索逻辑。如果学生管理你完整写下来还有余力,用同样套路把班级列表、新增、删除做出来,整个作业的功能完整度就非常能打了。

5. 作业提交前一定要检查的问题清单

5.1 代码跑不起来:逐条对照排除

我见过太多人卡在项目启动阶段,页面一打开就是一大串红字,整个人直接懵掉。其实这些问题翻来覆去就那么几个根源,我整理了一个排查表,按这个顺序查能省不少时间。

现象可能原因处理方式
启动报Failed to configure a DataSource数据源配置没生效检查application.yml是否在resources目录,检查数据库url、用户名、密码
数据库连不上:Communications link failureMySQL服务没启动或端口不对检查MySQL服务,确认端口3306没有被占用
数据库驱动加载失败MySQL版本和驱动类不匹配5.x用com.mysql.jdbc.Driver,8.x用com.mysql.cj.jdbc.Driver
页面显示Whitelabel Error PageController没有找到对应模板检查template路径、模板文件名、Controller返回值
页面报404路径写错或者视图解析器配置错误确认Controller的@RequestMapping和模板路径对应
页面样式全部丢失静态资源请求被拦截统一用th:href="@{/css/style.css}"引用,不要直接写绝对路径
中文显示乱码数据库连接字符串缺编码参数在url里加characterEncoding=utf8
修改了页面不生效Thymeleaf缓存未关闭设置spring.thymeleaf.cache=false并重启应用

这里面我觉得静态资源丢失是最隐蔽的一个。如果你的项目里配置了登录拦截器或者过滤器,它可能会把CSS、JS、图片这些静态资源的请求也拦下来,导致页面光秃秃的,已经不是样式难看的问题,而是功能都点不了。解决办法是放行静态资源路径,或者直接排除后缀匹配。检查时先看一眼浏览器F12控制台,如果加载css的请求状态是403或者被重定向,八成就是拦截器干的。

5.2 数据已经查到了,页面却显示不出来

这个坑也常见,尤其在使用Thymeleaf之后。你把数据放进了Model,页面也用th:each循环了,但浏览器里就是看不到内容,反复刷新都一样。

遇到这种问题,第一件事不是改前端,而是打开后端控制台看SQL日志。配置里加了log-impl: org.apache.ibatis.logging.stdout.StdOutImpl之后,每次查询都会在控制台打印SQL。你一看SQL,如果根本没有查数据库,说明Controller里没执行到查询方法;如果SQL执行了但resultType没对上,那要查实体类和表结构的对应关系。这个思路看起来简单,但能定位九成以上的“查不到数据”问题。

5.3 答辩前必做的三件事

功能全部跑通之后,不要急着提交。我事后复盘最受益的三个动作,今天也分享出来。

第一,把数据库里放满测试数据。别只留两三条空数据,至少造个十几二十条覆盖不同班级、不同关键字的数据,这样演示模糊查询和分页时才有说服力。造数据可以手写INSERT,也可以从前几次作业的数据里整理,别偷懒。

第二,把项目从零启动一遍,并且清除浏览器缓存后再演示。很多同学平时开发环境是热部署的,页面改了没察觉,结果答辩时用的是旧代码,演示时当场翻车。建议提交之前新建一个独立的目录,拉一份最新代码,重新mvn clean、install,然后启动跑一遍全流程。

第三,把每个页面的“边角料”都检查一遍:删除按钮有没有确认提示,编辑回显时原来的值能不能正确显示,搜索之后点下一页能不能带上搜索条件。这一类细节决定作业的完成度。比如搜索场景下,你的分页链接要记得把name参数带上,否则翻到第二页搜索条件就丢了,列表就变成全量数据,这个效果演示起来非常尴尬。

5.4 一些“问了必答”的答辩问题

第五次作业的答辩,老师很少会深挖高难度框架问题,他们更爱问“为什么”。我碰到的和听别人说的问题,大概率跳不出下面这几类,提前准备好就行。

  • 为什么学号字段要加唯一约束?——为了防止同一学号被录两次,保证数据唯一性。
  • 删除班级后学生数据怎么处理?——外键设置了ON DELETE SET NULL,学生记录保留,班级字段置空。
  • 模糊查询用LIKE,数据量大不会慢吗?——可以用索引,同时模糊查询要以学号这种固定前缀字段做索引才有效;这个场景考虑的是功能,不是极端性能。
  • 为什么前端用Thymeleaf而不是Vue?——服务端渲染部署简单,模板在工程内,适合课程项目规模。
  • 你分页的页码变了,搜索条件还在吗?——我做了条件拼接,保留了查询参数。

这些问题其实不难,但要求你对项目是真的理解,而不是背了几行代码。平时写代码多问自己几个为什么,答辩时基本能站稳。

6. 做完这次作业之后的一点体会

如果说这次作业让我印象最深的环节,那肯定是分页插件那次翻车。我本来以为把依赖倒进pom里就行,结果页面查出来的数据永远是全量,当时还以为自己写的SQL有问题,反反复复改了很久。后来无意间看到一条资料,才知道MyBatis-Plus在Spring Boot里还要主动注册分页插件,那一刻真的是“原来如此”的感觉。后来我教身边人做类似项目,第一件事就会提醒他:“先配分页拦截器,再写分页查询。”

还有一点想说的是,第五次作业这种规模的项目,其实特别适合用来培养“从零到一”的工程感。你不要把它当作期末冲刺,而是当成一个自由发挥的小试验场。比如你可以花一晚上把用户登录加上,用Session做简单的登录拦截;也可以写一个按年龄统计学生人数的小图表;甚至给管理系统加一个简单的文件上传功能,练习一下Spring Boot处理MultipartFile。这些都是加分项,但它们不应该是你焦虑的来源,基础功能稳了以后,再考虑这些锦上添花的东西。

最后再分享一个小技巧:完成作业之后,用数据库工具把你建的库导出一份SQL备份,放到项目根目录或者docs文件夹里。这样做的好处是,万一你机器上的MySQL数据表被改坏了,或者第二天要用另一台电脑演示,直接执行备份SQL就能把环境和数据完整还原,省去重新造数据的麻烦。这个小习惯在这次作业里帮过我一次,后来工作中导库导表也一直在用。希望这些经验能让你少走几步弯路,第五次作业顺利完成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询