首页 / 资讯中心 / 文章详情

JSP+Servlet学生信息管理系统:分层CRUD、分页搜索与登录拦截实战

JSP+Servlet学生信息管理系统:分层CRUD、分页搜索与登录拦截实战 ★ FEATURED ARTICLE
简介面向Java Web入门学习者和课程设计需求者的学生信息管理实战项目采用JSPServlet配合Layui、jQuery完成界面与交互后端基于MySQL存储数据支持学生自主注册登记、教师集中管理学生信息等基础流程适合用来理解Servlet请求处理、JSP页面渲染和分层协作。压缩包共2个文件以zip工程包和htm说明页为主工程包用于导入IDEA后配置Tomcat运行说明页便于快速了解项目结构与部署要点整体约2.13MB体量轻便。已有6437人学习下载说明其在同类课程设计资料中具有一定参考热度。读者可据此获得一套可直接运行的基础CRUD范例并借助数据库脚本、lib依赖和页面模块梳理登录注册、信息维护、列表查询等实现思路适合作为期末课程设计、Java Web入门练习或二次扩展的起点。1. 需求先立住为什么学生信息管理系统用 JSP Servlet 而不是直接上框架选题会上常有人问都什么年代了还写 JSP Servlet换 Spring Boot 不是更体面我的看法相反对一份 Java Web 课程设计来说JSP Servlet 恰好是最能讲清原理的选择——请求怎么进来、会话怎么保持、页面怎么拿到数据全在自己写的几行代码里没有框架替你兜底。这套简单学生信息管理系统要解决的问题很具体用最少的依赖把登录、学生信息增删改查、分页列表和关键字搜索跑通代码分层清楚到能一页页讲给答辩老师听。它适合三类人交 Java Web 课程设计的同学、需要一份能跑的基础版毕设的人、刚学完 Servlet 想找完整项目练手的人。规模不大但每一层都能落地。2. 总体架构设计三层结构与五类源文件的职责划分2.1 工程目录与分层边界JSP Servlet 项目最容易出的问题不是功能写不出来而是写着写着全塞进一个 Servlet 里三百行 doPost改个字段要翻半天。所以开工第一件事是把目录定死后面所有代码只往对应格子里放。我一般按「页面 → 控制 → 数据访问 → 实体」四段切Web 层只做参数接收和转发业务判断放 ServiceSQL 全部收进 DAO实体类只当数据搬运工。标准 Maven Web 工程或普通 Dynamic Web Project 都适用目录大致如下sms/ ├── src/main/java/com/demo/ │ ├── pojo/ # 实体类Student、SysUser、Clazz │ ├── dao/ # 数据访问StudentDao、UserDao │ ├── service/ # 业务组装StudentService │ ├── servlet/ # 控制器LoginServlet、StudentServlet │ ├── filter/ # 登录校验LoginFilter、EncodingFilter │ └── util/ # 工具JdbcUtils、PageBean ├── src/main/webapp/ │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 第三方 jar │ ├── jsp/ # 页面login.jsp、student_list.jsp、student_edit.jsp │ ├── css/ js/ │ └── index.jsp └── sql/sms.sql # 建表与初始数据脚本dao只认 SQL 和 ResultSet不认 HttpServletRequestservlet只认参数和转发不写一句 SQL。这条边界守住任何一个功能出问题都能快速定位到是「页面没取到值」还是「SQL 条件写错」。很多同学的代码之所以改一处崩三处就是因为页面里直接new StudentDao()查库跳过了控制层参数校验和异常处理全丢了。WEB-INF/lib下需要放的 jar 不多MySQL 驱动、JSTL 实现包jstl-1.2.jar 与 standard.jar。这两个包忘记放页面上的c:forEach会原样输出到浏览器看起来像标签没生效实际是 jar 没加载。2.2 一次请求的完整链路以「打开学生列表」为例链路是这样的浏览器请求/student?actionlistpageNum1先被 Filter 拦下判断 Session 里有没有登录用户没有就重定向到login.jsp通过后进入StudentServletdoGet里解析 action调StudentService.findByPage()Service 调 DAO 执行两条 SQL——一条count求总数一条带limit取当前页DAO 把 ResultSet 映射成ListStudent装进PageBean返回Servlet 把 PageBean 放进request作用域forward到student_list.jspJSP 用 EL 和 JSTL 把数据铺开成表格。这条链路有两个关键约定。第一查询类请求用forward数据靠request.setAttribute传递URL 不变刷新只是重新查一次没有副作用。第二增删改这类写操作完成后必须sendRedirect回列表页也就是常说的 PRG 模式否则用户按 F5 会把新增再提交一遍数据库里凭空多出一条重名记录。这一点后面避坑章节还会展开。会话方面登录成功后把SysUser对象塞进session整个应用只认这一个 key。别在 Filter 里既查 Session 又查 Cookie也别把用户 id 拼在 URL 上传着走——一旦链接被复制权限就跟着链接走了。2.3 技术版本组合与 web.xml 配置版本组合建议整套对齐别东拼西凑组件建议版本说明JDK8Tomcat 8.5/9 与 JDK 8 兼容性最好Tomcat8.5 或 99 的 Servlet 版本为 4.0注解支持完整MySQL5.7 或 8.08.0 必须用com.mysql.cj.jdbc.DriverServlet API4.0由 Tomcat 提供不要打进 war 包JSTL1.2配合 EL 表达式使用Servlet 3.0 之后可以用WebServlet和WebFilter注解省掉一堆 XML。但答辩时老师常问「映射关系在哪配的」所以我一般保留web.xml的骨架把欢迎页、错误页、Session 超时写进去Servlet 映射用注解两边都讲得通?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-namestudent-info-system/display-name welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list !-- 会话 30 分钟过期避免演示时登录态一直保留 -- session-config session-timeout30/session-timeout /session-config !-- 统一错误页避免把 SQL 异常堆栈直接甩给用户 -- error-page error-code500/error-code location/jsp/error.jsp/location /error-page /web-appsession-timeout的单位是分钟写 30 就是半小时无操作自动失效。error-page这一条在演示时特别有用万一现场数据库没起来页面跳的是友好提示而不是一片红字堆栈观感差别很大。至于编码问题Tomcat 8 之后 GET 默认按 UTF-8 解析POST 仍需在读取参数前设置编码这部分放到 Filter 里统一处理最省心。3. 数据库与实体设计三张核心表与 DAO 层的边界3.1 建表 SQL 与字段取舍学生信息管理系统的表不用多三张足够撑起一个完整课程设计学生表、用户表、班级表。学生表是主表班级表提供下拉选项用户表管登录。字段设计上有个小取舍值得讲学号用varchar而不是自增 int因为真实场景里学号常常带年份前缀而且学号本身就该是业务主键用自增 id 当主键、学号加唯一索引是更稳妥的做法。CREATE DATABASE IF NOT EXISTS sms DEFAULT CHARSET utf8mb4; USE sms; -- 班级表给下拉框用避免学生表里存班级名字符串 CREATE TABLE clazz ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, major VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 学生表student_no 唯一防止重复录入 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender CHAR(1) NOT NULL DEFAULT 男, birthday DATE, clazz_id INT, phone VARCHAR(20), enroll_date DATE, remark VARCHAR(200), CONSTRAINT fk_student_clazz FOREIGN KEY (clazz_id) REFERENCES clazz(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户表密码存 MD5 摘要不存明文 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(30) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO sys_user(username, password, real_name) VALUES (admin, 21232f297a57a5a743894a0e4a801fc3, 管理员);utf8mb4而不是utf8是因为后者在 MySQL 里其实只有三字节存不了 emoji 和部分生僻字备注字段一旦出现特殊字符就会插入失败。唯一索引加在student_no上重复录入学号时数据库直接报错比在 Java 里先查一遍再插入更可靠——并发场景下「先查后插」本身就有空窗。初始化账号的密码是admin的 MD5 值。课程设计阶段用 MD5 摘要够用至少数据库里不是明文答辩被问「密码怎么存的」有话可说真要上生产得换加盐哈希这点在报告里写一句反而显得考虑过。3.2 实体类与 DAO 封装实体类就是一个字段对应一列、提供无参构造和 getter/setter 的 POJO不加任何逻辑。DAO 层则要解决两个重复劳动拿连接、关资源。写一个JdbcUtils把这两件事收掉所有 DAO 方法只专注 SQL 和结果映射。package com.demo.util; import java.sql.*; public class JdbcUtils { // MySQL 8 驱动类名带 cj5.x 是 com.mysql.jdbc.Driver private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/sms?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai; private static final String USER root; private static final String PWD 123456; static { try { Class.forName(DRIVER); // 静态块只加载一次驱动 } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(数据库驱动未找到检查 WEB-INF/lib 下是否有 mysql-connector); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } // try-with-resources 保证连接、语句、结果集一定关闭 public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { } } } }URL 里三个参数都是血的教训useUnicode和characterEncoding决定中文能不能正确写进库serverTimezone不写会直接抛时区异常useSSLfalse免掉本地环境的一堆握手警告。Class.forName放在静态块里工程启动后只执行一次比每个方法里都写一行强。DAO 方法统一长这样以按条件分页查询为例public ListStudent findByPage(String keyword, int offset, int pageSize) throws SQLException { StringBuilder sql new StringBuilder( SELECT s.*, c.name AS clazz_name FROM student s LEFT JOIN clazz c ON s.clazz_id c.id WHERE 11 ); ListObject params new ArrayList(); // 关键字同时匹配学号和姓名用 ? 占位防注入 if (keyword ! null !keyword.trim().isEmpty()) { sql.append(AND (s.student_no LIKE ? OR s.name LIKE ?) ); params.add(% keyword.trim() %); params.add(% keyword.trim() %); } sql.append(ORDER BY s.id DESC LIMIT ?, ?); params.add(offset); params.add(pageSize); ListStudent list new ArrayList(); try (Connection conn JdbcUtils.getConn(); PreparedStatement ps conn.prepareStatement(sql.toString())) { for (int i 0; i params.size(); i) { ps.setObject(i 1, params.get(i)); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setGender(rs.getString(gender)); s.setPhone(rs.getString(phone)); s.setClazzName(rs.getString(clazz_name)); // 联表带出的班级名 list.add(s); } } } return list; }WHERE 11是个老写法作用是在后面无脑拼AND而不必判断第一个条件。LEFT JOIN是为了列表页直接显示班级名称否则页面上只有clazz_id还得再查一次。用PreparedStatement占位符而不是字符串拼接不只是防注入还能让 MySQL 复用执行计划这个点在答辩里是加分项。3.3 分页查询与总数统计分页要两个数总记录数和当前页数据。总记录数决定上一页下一页的边界当前页数据用LIMIT offset, size取。我习惯把这两个数连同页码一起封装成PageBeanServlet 只需往 request 里塞一个对象JSP 直接遍历。package com.demo.util; import java.util.List; public class PageBeanT { private int pageNum; // 当前页从 1 开始 private int pageSize; // 每页条数 private int totalCount; // 总记录数 private int totalPage; // 总页数由 totalCount 与 pageSize 算出 private ListT list; // 当前页数据 public PageBean(int pageNum, int pageSize, int totalCount, ListT list) { this.pageSize pageSize; this.totalCount totalCount; this.list list; // 总页数向上取整0 条数据时也保留 1 页避免页码显示为 0 this.totalPage Math.max(1, (totalCount pageSize - 1) / pageSize); // 页码越界钳制防止用户手改 URL 参数导致 offset 为负 this.pageNum Math.min(Math.max(1, pageNum), this.totalPage); } public int getOffset() { return (pageNum - 1) * pageSize; } // 省略其余 getter }页码钳制这一步很多人省掉结果用户把 URL 里的pageNum改成 -1LIMIT -10, 10直接抛 SQL 语法错误。totalCount为 0 时把总页数兜底成 1页面上就不会出现「第 1 页 / 共 0 页」这种自相矛盾的显示。Service 层的组装逻辑是先算 offset查总数再查列表最后 new 一个 PageBean 返回三层职责一目了然。4. 核心功能实现登录拦截、学生 CRUD 与 JSP 渲染4.1 登录态与 Filter 鉴权登录本身很简单取用户名密码MD5 后比库成功就session.setAttribute(loginUser, user)并重定向到列表页失败回登录页带一句提示。真正需要设计的是「怎么保证没登录的人进不来」。答案是在每个受保护请求前加一道 Filter而不是在每个 JSP 顶部写一段if (session null) response.sendRedirect(...)——后者漏一个页面就是一个洞。WebFilter(/*) public class LoginFilter implements Filter { // 白名单登录页、登录接口、静态资源不拦截 private static final ListString WHITE Arrays.asList( /login.jsp, /login, /css/, /js/, /error.jsp); Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 多个 Filter 时统一在这里设编码必须早于任何 getParameter request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); String uri request.getRequestURI().substring(request.getContextPath().length()); boolean isWhite WHITE.stream().anyMatch(uri::startsWith); if (isWhite || request.getSession().getAttribute(loginUser) ! null) { chain.doFilter(req, resp); // 放行 } else { // 未登录统一回登录页而不是弹一个空白页 response.sendRedirect(request.getContextPath() /login.jsp); } } }白名单用startsWith匹配能覆盖/css/下所有样式文件.do之类的后缀映射不在白名单里一律要求登录。把编码设置也放进这个 Filter是为了避免在每个 Servlet 的doPost第一行重复写setCharacterEncoding少写一处就是一次乱码事故。注意setContentType在 Filter 里设了之后JSP 页面头部的pageEncoding仍要写两者管的不是一回事。4.2 学生模块的 Servlet 分发一个模块一个 Servlet用action参数区分动作是 JSP Servlet 时代最常用的写法。比起给增删改查各建一个 Servlet它少了四个类路由逻辑集中在一处答辩时也好讲。WebServlet(/student) public class StudentServlet extends HttpServlet { private final StudentService service new StudentService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (action null) action list; switch (action) { case list: // 列表 分页 关键字 list(req, resp); break; case toEdit: // 进入编辑页回显单条数据 req.setAttribute(student, service.findById(parseInt(req.getParameter(id)))); req.getRequestDispatcher(/jsp/student_edit.jsp).forward(req, resp); break; case delete: // 删除后重定向防止刷新重复删除 service.delete(parseInt(req.getParameter(id))); resp.sendRedirect(req.getContextPath() /student?actionlist); break; default: resp.sendError(404); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (save.equals(action)) { Student s new Student(); s.setId(parseInt(req.getParameter(id))); // 隐藏域回传0 表示新增 s.setStudentNo(req.getParameter(studentNo)); s.setName(req.getParameter(name)); s.setGender(req.getParameter(gender)); s.setPhone(req.getParameter(phone)); s.setClazzId(parseInt(req.getParameter(clazzId))); service.saveOrUpdate(s); resp.sendRedirect(req.getContextPath() /student?actionlist); } } private int parseInt(String v) { try { return Integer.parseInt(v); } catch (Exception e) { return 0; } } private void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int pageNum Math.max(1, parseInt(req.getParameter(pageNum))); String keyword req.getParameter(keyword); PageBeanStudent page service.findByPage(keyword, pageNum, 8); req.setAttribute(page, page); req.setAttribute(keyword, keyword); // 回填搜索框翻页时不丢条件 req.getRequestDispatcher(/jsp/student_list.jsp).forward(req, resp); } }几个容易忽略的点parseInt包一层 try-catch参数缺失时返回 0 而不是抛 500删除和保存之后一律sendRedirect走 PRG 模式列表页把keyword也塞回 request否则用户翻到第二页搜索词就丢了。saveOrUpdate内部按 id 是否为 0 决定 insert 还是 update新增和修改共用一套表单页面只做一次。4.3 JSP 用 JSTL 与 EL 渲染列表JSP 页面里不要出现% %脚本片段这是最容易让代码变脏的地方。取值用 EL循环和判断用 JSTL页面就只剩标签结构% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table classtable trth学号/thth姓名/thth性别/thth班级/thth电话/thth操作/th/tr c:forEach items${page.list} vars tr td${s.studentNo}/td td${s.name}/td td${s.gender}/td td${s.clazzName}/td td${s.phone}/td td a href${pageContext.request.contextPath}/student?actiontoEditid${s.id}编辑/a a href${pageContext.request.contextPath}/student?actiondeleteid${s.id} onclickreturn confirm(确认删除该学生)删除/a /td /tr /c:forEach /table !-- 分页条翻页时带上 keyword搜索条件不丢 -- div classpager 第 ${page.pageNum} / ${page.totalPage} 页共 ${page.totalCount} 条 c:if test${page.pageNum 1} a href${pageContext.request.contextPath}/student?actionlistpageNum${page.pageNum - 1}keyword${keyword}上一页/a /c:if c:if test${page.pageNum page.totalPage} a href${pageContext.request.contextPath}/student?actionlistpageNum${page.pageNum 1}keyword${keyword}下一页/a /c:if /div所有链接都拼pageContext.request.contextPath是因为部署到 Tomcat 后项目名会成为路径前缀写死/student在本地能用换台机器就 404这个坑几乎每届都有人踩。c:forEach的var名和 EL 表达式里的名字必须一致写错不会报错只是页面空白。删除加一句confirm演示时手滑点错也不至于把数据删光。5. 避坑与排查五个最容易翻车的点5.1 中文乱码POST 和 GET 是两套机制现象表单提交的中文姓名存进数据库变成???或者页面上显示成问号方块。原因分两种POST 请求的体编码由request.setCharacterEncoding(UTF-8)决定且必须在第一次getParameter之前调用写在之后完全无效GET 请求的参数在 URL 里由 Tomcat 的Connector决定Tomcat 8 以后默认 UTF-8但老项目或改过server.xml的环境仍可能是 ISO-8859-1。解决办法把setCharacterEncoding提到 Filter 最前面跳转页面用response.setContentType(text/html;charsetUTF-8)GET 有问题就去server.xml的 Connector 上加URIEncodingUTF-8。另外数据库和表的字符集必须是utf8mb4连接串带characterEncodingutf8三处对齐才不会漏。5.2 连接泄漏跑几分钟就卡死现象一开始功能全正常反复刷新列表几十次后页面转圈日志里出现Too many connections。原因基本是某个 DAO 方法里Connection开了没关或者ResultSet关了但PreparedStatement漏了。解决办法全部改用 try-with-resources把Connection、PreparedStatement、ResultSet写在 try 的括号里异常分支也会自动关闭。排查时在JdbcUtils.getConn()里打一行日志打印当前连接数或者直接show processlist看哪个连接一直 Sleep 不释放顺藤摸瓜就能找到漏关的那一处。5.3 登录绕过页面能直接输地址进去现象不登录直接在地址栏敲/jsp/student_list.jsp页面居然打开了只是数据是空的。原因是 Filter 只拦了/student没拦 JSP 页面而 JSP 里又直接调了 DAO 或者显示空表。解决办法有两条路一是 Filter 映射写/*并把白名单做全二是把 JSP 页面全部挪进WEB-INF/jsp/下这个目录浏览器无法直接访问只能由 Servletforward进去。我一般两个都做Filter 兜底WEB-INF再上一道锁。注意挪进WEB-INF后页面里的相对路径引用 CSS 会失效得改用${pageContext.request.contextPath}/css/...的绝对路径。5.4 刷新重复提交一次新增变三条现象点「保存」后按 F5 刷新数据库里多出一条一模一样的学生记录学号唯一索引还会抛异常。原因是保存完用forward转到列表页浏览器地址栏还停在POST /student上刷新就是重发一次 POST。解决办法是严格 PRG写操作完成后一律response.sendRedirect()到列表查询地址浏览器收到 302 会自动发 GET刷新只是重新查询。另外可以在保存表单里放一个隐藏的 token提交时校验一次不过对课程设计来说 PRG 已经够了。5.5 修改变全表更新主键丢了现象编辑某条学生信息保存后发现所有学生的姓名都变成了同一个。原因是更新语句写成了UPDATE student SET name? WHERE name?或者编辑页的隐藏域input typehidden nameid value${student.id}里${student.id}是空parseInt返回 0SQL 条件退化成匹配全部。解决办法更新一律用主键WHERE id?隐藏域的值在页面上用开发者工具确认非空DAO 层在id 0时直接抛异常拒绝执行。这类问题一旦发生就是事故级改数据都改不回来所以我的习惯是 update 方法第一行先断言 id 有效性。6. 验收与答辩一张检查表把功能跑通并讲清设计取舍功能写完不等于能交。答辩现场最尴尬的不是被问难题而是老师随手点了一个按钮就报错。所以我一般按下面这张表逐项过一遍每项都要亲手点、亲手填不能靠「应该没问题」检查项操作路径预期结果常见失败未登录拦截直接访问/student?actionlist跳回登录页Filter 白名单写错误放行登录失败提示输入错误密码停留登录页并提示提示信息没回填页面空白新增学生填全字段点保存列表首行出现新记录中文乱码、学号重复报 500修改学生点编辑改电话保存对应行数据变化id 丢失导致全表更新删除学生点删除并确认该行消失总数减一用了 GET 删除被浏览器预加载关键字搜索输入姓氏搜索只显示匹配记录翻页后搜索词丢失分页边界首页点上一页、末页点下一页按钮不显示或不可点offset 为负抛 SQL 异常空数据清空表后打开列表显示「暂无数据」页面报空指针过完表还有一件事把项目从 IDE 里删掉重新部署一次 war 包到干净的 Tomcat跑同一套流程。这一步能暴露八成「本地能跑、拷过去就崩」的问题通常是WEB-INF/lib少 jar、数据库连接串还是本机密码、或者 JSP 路径写成了绝对磁盘路径。演示前把数据库脚本sms.sql也准备一份现场换机器时导入就能用。答辩讲什么其实就三个问题。第一为什么这么分层——页面只管展示、Servlet 只管调度、DAO 只管 SQL任何一层改动不牵连其他层这是可维护性的最小代价。第二登录鉴权为什么放 Filter 而不是每个页面判断——集中一处漏不掉。第三分页为什么用两条 SQL——count求总数、limit取当页比一次性查全量再在内存里截断省内存数据量大了差别明显。这三个回答对应的是设计意识比背 API 有用得多。我第一次带这类课设时学生演示到一半数据库连不上现场重装 MySQL 装了二十分钟。从那以后我每次交付前都强制走一遍「换机器部署 空库导入 全表点击」的流程宁可多花一小时也不在台上赌运气。这份 JSP Servlet 学生信息管理系统的完整工程、建表脚本和部署说明已经整理成一份可直接导入的资料照着目录结构替换包名就能改成自己的题目。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站