简介:这是一套基于 Java Web 的简单学生信息管理系统,采用 JSP + Servlet 经典架构,搭配 layui 与 jQuery 构建前端界面,并以 MySQL 作为数据存储。项目面向正在完成课程设计或初学 Java Web 的在校学生,实现了学生自主注册登记、教师管理学生等核心功能,可帮助理解“JSP 页面—Servlet 控制—数据库操作”的完整开发链路。压缩包内共 2 个文件,主文件为 zip 格式的完整工程源码,附带一个 htm 格式的 readme 说明页,整体大小仅 2.13MB,结构轻量、便于下载后快速搭建与调试;源码工程中已在 lib 文件夹内置所需 jar 包,并额外提供数据库脚本文件的网盘链接。资源运行环境为 JDK7 + Tomcat8 + MySQL + IntelliJ IDEA,已有 6431 人学习/下载,适合作为 Java Web 课程设计的参考模板,也可用于练习 JSP/Servlet 与 MySQL 的整合开发,以及 layui 表格、弹窗等前端组件的实际应用。 上周带一个学弟过他的Java Web课程设计,题目是"学生信息管理系统",技术栈指定jsp+servlet。这个项目听起来再普通不过,他却硬生生在Tomcat部署和中文乱码上耗了快一个周末,最后我陪他把代码一行行捋完,才发现问题全出在那些"大家都这么写但没人讲为什么"的地方。
这篇文章就借这个学生信息管理系统,把jsp+servlet从浏览器请求到数据库、再到页面渲染的整条链路拆开讲清楚。你会看到完整的项目结构、核心代码、数据库表设计,以及我在排查过程中踩过的那些典型坑。适合刚学完Java SE,准备做第一个完整Web项目的初学者;也适合正在交JSP+Servlet课程设计作业、希望能真正讲明白自己代码的同学。
1. 为什么还要写jsp+servlet:这门"老技术"其实是理解Spring Boot的钥匙
1.1 Servlet和JSP到底各自负责什么
很多同学一上来就质疑:现在外面公司不都用Spring Boot了吗,为什么课程设计还用JSP+Servlet这种"老古董"?
这里要说个反直觉的事实:Servlet和JSP是Java Web最底层的两个组件,Spring MVC也好,Spring Boot也好,本质都是在它们外面包了一层又一层的壳。你学这个项目的时候,能看到一个HTTP请求怎么被接收、参数怎么被取出、数据库连接怎么被管理、页面怎么被响应,这些底层感知在框架里反而看不清楚。等你后面用Spring Boot,遇到请求路径写错、参数接收不到、响应乱码之类的问题,脑子里会自动浮现这套Servlet的模型,排查起来快得多。
这套组合里,Servlet负责写Java逻辑,处理请求和响应;JSP负责生成HTML页面。早期Java Web是直接在JSP里写Java代码的,后来发现这样维护起来太痛苦,于是把逻辑抽到Servlet里,JSP只负责展示。这其实就是MVC思想的雏形:JSP是View,Servlet是Controller,DAO和JavaBean是Model。你把这个项目吃透,再去看Spring MVC的DispatcherServlet、Controller、ModelAndView,会发现一切都很眼熟。
1.2 这个项目的学习边界
学生信息管理系统本质上就是一个增删改查系统,核心是四个字:列表查询、新增、编辑、删除。这四个操作覆盖了Web开发最常用的HTTP GET/POST、JDBC连接、参数传递、请求转发和重定向,不复杂,但链路完整。
建议你不要把这个项目当成"作业",而是当成一张地图。这张地图里每个点都是真实的Web开发会遇到的问题:数据库连接怎么管理、SQL怎么防注入、中文乱码在哪一层处理、刷新页面为什么会导致重复提交。弄懂这些问题,比单纯用Spring Boot从一个模板项目里把CRUD跑通有意义得多。
2. 项目骨架、依赖和数据库:动手前先把边界定死
2.1 目录结构与分层约定
我的建议是直接按Maven的目录结构来,不用纠结IDE里到底是叫WebContent还是webapp。包结构分成四层就够了:
src/main/java com.myschool.entity.Student com.myschool.dao.StudentDao com.myschool.util.DBUtil com.myschool.servlet.StudentListServlet com.myschool.servlet.StudentAddServlet com.myschool.servlet.StudentEditServlet com.myschool.servlet.StudentDeleteServlet src/main/webapp list.jsp add.jsp edit.jsp WEB-INF/web.xml注意这个项目我刻意没有写Service层。逻辑实在太薄,Servlet直接调DAO完全hold得住。如果你为了"看起来规范"硬加一层StudentService,你会发现它只在Servlet和DAO之间当了一回传声筒。判断标准很简单:当业务逻辑需要事务、需要组合多个DAO操作时,再引入Service层不迟。
2.2 数据库表和初始化数据
表字段不要贪多,够用且真实就行。我平时带人做课设,学生表就六个字段:主键、学号、姓名、性别、年龄、专业。
CREATE DATABASE IF NOT EXISTS school_db DEFAULT CHARACTER SET utf8mb4; USE school_db; CREATE TABLE t_student ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', sno VARCHAR(20) NOT NULL COMMENT '学号', name VARCHAR(50) NOT NULL COMMENT '姓名', gender VARCHAR(10) DEFAULT '男' COMMENT '性别', age INT DEFAULT 18 COMMENT '年龄', major VARCHAR(50) DEFAULT NULL COMMENT '专业' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表'; INSERT INTO t_student(sno, name, gender, age, major) VALUES ('2025001', '张三', '男', 20, '计算机科学与技术'), ('2025002', '李四', '女', 21, '软件工程');性别这里直接存字符串"男/女",对课设完全没问题。如果想体现设计能力,可以存tinyint 0/1再在页面映射,但那属于锦上添花,不改变系统本质。字符集方面,表一定要用utf8mb4而不是utf8,不然遇到生僻字或者emoji(当然我写博文不用emoji,但用户数据里可能有人输入)会报错或者存不进去。
2.3 Java Web的技术选型细节
环境方面,我用的是JDK 8 + Tomcat 8.5 + MySQL 8.0。这里有个特别容易踩的版本坑:如果你用的是Tomcat 10以上,Servlet的包名从javax.变成了jakarta.,网上很多教程代码直接粘过来是编译不过的。建议你图省心直接用Tomcat 8.5或9,老老实实跟着javax写。
MySQL驱动jar包,如果是Maven项目就在pom.xml里加依赖;如果不是Maven,手动把mysql-connector-java这个jar扔到WEB-INF/lib目录下。这一步漏掉,后面必现ClassNotFoundException。至于还有同学问clickhouse之类的驱动怎么在Web项目里连接,原理完全一样:把对应驱动jar放进lib目录,Class.forName加载驱动类,后面就是JDBC连接的套路了。
3. 列表查询全链路:一个请求从浏览器到数据库再回到页面的完整旅程
3.1 DBUtil:最容易被写糊的一层
我从这个项目的DAO说起,因为它是所有DAO操作的地基。看代码之前,先要理解JDBC连接对象的获取逻辑。
package com.myschool.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/school_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }这段代码里有几个细节要解释。首先,连接URL里的characterEncoding=utf8是后面解决中文乱码的关键之一,少了它,即使页面和Servlet都设置对了,写入数据库的中文也可能变成问号。其次,MySQL 8.x的驱动类是com.mysql.cj.jdbc.Driver,而MySQL 5.x是com.mysql.jdbc.Driver,版本不一样,类名不一样。JDBC 4.0之后其实可以省略Class.forName这一步,驱动包会有SPI自动加载,但写上更稳妥,也不容易让初学者困惑。
3.2 StudentDao和PreparedStatement
DAO层我习惯每个方法对应一种数据操作。先看查询列表和按ID查询:
package com.myschool.dao; import com.myschool.entity.Student; import com.myschool.util.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class StudentDao { public List<Student> list() { String sql = "SELECT * FROM t_student ORDER BY id DESC"; List<Student> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setSno(rs.getString("sno")); stu.setName(rs.getString("name")); stu.setGender(rs.getString("gender")); stu.setAge(rs.getInt("age")); stu.setMajor(rs.getString("major")); list.add(stu); } } catch (SQLException e) { e.printStackTrace(); } return list; } }我一直强调用PreparedStatement而不是直接拼接字符串,这不仅是为了防止SQL注入,也是为代码可读性。如果在真实企业里,SQL拼接引号少打一个,整个功能就挂了,排查起来心态会崩。ResultSet转Java对象这一步,没有花活,就是把列名对应到实体类的setter。
try-with-resources语法是我推荐的方式,Connection、PreparedStatement、ResultSet都会自动关闭,不会出现连接泄漏。很多老教程亲手写conn.close(),一旦中间抛异常或漏写,连接池很快就被耗光。你作为初学者,直接从好习惯开始比以后纠正容易得多。
3.3 Servlet接请求和转发
Servlet这里,我用的是Servlet 3.0的@WebServlet注解方式,不用去web.xml里配置映射。有同学想了解web.xml方式怎么配置,文章后面会专门讲和注解的区别。
package com.myschool.servlet; import com.myschool.dao.StudentDao; import com.myschool.entity.Student; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/student/list") public class StudentListServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); List<Student> list = studentDao.list(); req.setAttribute("studentList", list); req.getRequestDispatcher("/list.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }重点看最后两行:forward(转发)和sendRedirect(重定向)的区别,是Java Web必考也是必懵的点。这里用forward是因为Servlet和JSP属于同一个请求生命周期:Servlet查完数据,把List塞进request域,然后告诉Tomcat"接下来让list.jsp来渲染",整个过程浏览器地址栏不会变,request里的studentList在JSP里能直接取到。
如果这里误用sendRedirect,浏览器会重新发起一个新请求,request域里的数据直接丢失,JSP里取出来就是null。这个区别值得你在本子上写一遍:转发是同一次请求内的"内部调岗",重定向是"你找隔壁部门"。
3.4 list.jsp把数据渲染成表格
JSP页面里取Servlet放进去的数据,推荐用EL表达式和JSTL标签。这里先用一个简洁版本:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>学生信息列表</title> </head> <body> <h2>学生信息列表</h2> <table border="1" cellpadding="8" cellspacing="0"> <tr> <th>ID</th> <th>学号</th> <th>姓名</th> <th>性别</th> <th>年龄</th> <th>专业</th> <th>操作</th> </tr> <c:forEach items="${studentList}" var="stu"> <tr> <td>${stu.id}</td> <td>${stu.sno}</td> <td>${stu.name}</td> <td>${stu.gender}</td> <td>${stu.age}</td> <td>${stu.major}</td> <td> <a href="student/edit?id=${stu.id}">编辑</a> <a href="student/delete?id=${stu.id}" onclick="return confirm('确定要删除该学生吗?')">删除</a> </td> </tr> </c:forEach> </table> <a href="student/add">新增学生</a> </body> </html>注意两点。第一,使用JSTL需要引入jstl的jar包,Tomcat不会自动带这个依赖;不加jar包,页面会报"Unable to find taglib"错误。第二,很多人第一次写容易把href写成/list.jsp,跳过了Servlet直接访问JSP。这样虽然能看到页面,但studentList属性是空的,一个学生都显示不出来。前端永远只和Servlet的URL打交道,不要直接去访问JSP文件,这样才能保证数据是从Servlet完整带过来的。
4. 新增、编辑、删除:表单请求怎么写才不容易乱
4.1 新增:doGet显示表单,doPost处理提交
到了表单操作,一个新规则出现了:同一个URL,可以用doGet和doPost分别应对不同场景。新增学生页面用/student/add这个地址,当浏览器以GET方式访问它时,我们返回add.jsp表单;当用户填写完点击提交,表单以POST方式发回来时,Servlet负责收参数、调DAO、再重定向回列表。
@WebServlet("/student/add") public class StudentAddServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/add.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); Student stu = new Student(); stu.setSno(req.getParameter("sno")); stu.setName(req.getParameter("name")); stu.setGender(req.getParameter("gender")); stu.setAge(Integer.parseInt(req.getParameter("age"))); stu.setMajor(req.getParameter("major")); studentDao.insert(stu); resp.sendRedirect("student/list"); } }add.jsp的表单里,action写"student/add",method写"post"。这里有个小坑:action路径是相对当前URL解析的。如果列表页在/student/list,表单在/student/add渲染,页面里的相对路径可能会解析错。我建议一律写项目上下文路径开头的绝对路径,比如${pageContext.request.contextPath}/student/add,这样无论从哪个页面发起都不会迷路。虽然课设里偷懒也能跑通,但这个好习惯能让你以后少踩一堆路径的坑。
4.2 编辑:id怎么在请求之间传递
编辑功能比新增多了一步"回显"。列表页点"编辑"链接,URL是/student/edit?id=3,注意这里的id是查询参数。编辑Servlet的doGet要做两件事:拿id、调DAO查单个学生、把student对象放进request、转发到edit.jsp。然后edit.jsp把Student的属性填进input标签的value属性里,用户看到的就是填好信息的表单。
@WebServlet("/student/edit") public class StudentEditServlet extends HttpServlet { private StudentDao studentDao = new StudentDao(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id = Integer.parseInt(req.getParameter("id")); Student stu = studentDao.findById(id); req.setAttribute("stu", stu); req.getRequestDispatcher("/edit.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); Student stu = new Student(); stu.setId(Integer.parseInt(req.getParameter("id"))); stu.setSno(req.getParameter("sno")); stu.setName(req.getParameter("name")); stu.setGender(req.getParameter("gender")); stu.setAge(Integer.parseInt(req.getParameter("age"))); stu.setMajor(req.getParameter("major")); studentDao.update(stu); resp.sendRedirect("student/list"); } }很多初学者做编辑时,会让编辑Servlet的doGet去调用新增页面的add.jsp,想着共用一个表单页面。这样不是不行,但你必须处理"新增时所有input为空、编辑时input有值"的差异。最省心是单独写一个edit.jsp,结构几乎和add.jsp一样,只是input的value里用${stu.xxx}做回显,form的action指向同一个/student/edit。表单里还得埋一个隐藏域,否则提交时Servlet不知道在更新哪条记录。
还有个隐藏的坑,也是新手必踩:Integer.parseInt对"空字符串"和"非数字字符串"会抛NumberFormatException。如果页面上年龄输入框留空提交,Servlet直接500。要么前端加required校验,要么Servlet里做非空判断,建议两者都做。
4.3 删除:为什么我建议不要用纯GET链接
删列表页上的"删除",我一开始写的也是<a href="student/delete?id=1">,这种GET请求方式方便,配合onclick confirm弹窗,课设演示完全够用。但从真实开发的角度说,删除属于修改操作,应该用POST。GET请求会被浏览器预加载、会被搜索引擎爬虫访问、也容易被恶意构造链接诱导点击,这些都可能造成意外的数据删除。
最稳妥的课设写法是做一个隐藏的表单,删除按钮触发这个表单的POST提交:
<form action="student/delete" method="post" style="display:inline;" onsubmit="return confirm('确定要删除该学生吗?');"> <input type="hidden" name="id" value="${stu.id}"> <button type="submit">删除</button> </form>对应Servlet里就只实现doPost,doGet直接返回405或者也转发过去。这样做还有个好处:地址栏里看不到被删的id,刷新页面不会触发二次删除。
4.4 转发和重定向的选择:防止刷新重复提交
在新增、编辑、删除的doPost末尾,我都用resp.sendRedirect("student/list"),而不是forward到list.jsp。原因是浏览器如果在表单提交后收到的是一个转发响应,地址栏还停留在/student/add,这时候用户按F5刷新,浏览器会再次向/student/add发送POST请求,于是数据库多出一条重复记录。
sendRedirect返回的是302响应,浏览器自动发起新的GET请求跳转到/student/list,地址栏变成列表地址,再按F5也只是重新查询列表,不会重复提交。这一行代码,成了区分"能跑"和"能用"的关键。
5. 这五个高频报错和乱码,我建议你提前收藏
5.1 中文乱码:三个位置必须同时管
中文乱码是JSP+Servlet项目里出现频率最高的噩梦,而且它不是单一原因导致的。我见过一个同学改了一下午,最后发现三个原因叠加。完整的检查顺序应该是:
第一层,页面本身。JSP文件顶部必须有<%@ page contentType="text/html;charset=UTF-8" language="java" %>,HTML的head里也建议写上charset=UTF-8。第二层,请求参数。POST请求的参数编码在Servlet里通过req.setCharacterEncoding("UTF-8")设置,注意这行必须在读取第一个参数之前执行。GET请求的参数编码是在Tomcat的server.xml里配置URIEncoding="UTF-8"。第三层,数据库。连接URL里指定characterEncoding=utf8,建表时使用utf8mb4。
为什么三处都要管?因为数据从浏览器到Servlet到数据库,每一层都有自己的编码规则,某一层还是默认的ISO-8859-1,中文字符就到那里断送了。排查的时候也别瞎试,在新增加一条中文数据后,分别看页面显示、Servlet里的值、数据库里的值,逐层缩小范围。
5.2 404和驱动类找不到的排查顺序
404是最让人摸不着头脑的问题。我建议按这个顺序排查:项目到底部署到Tomcat没;访问的URL和Servlet映射是否完全一致;页面里的href是否拼错。很多同学把@WebServlet("/student/list")写成了/student/lsit,或者列表页链接写成studentListServlet,这类错误一行行对着检查比重新部署快得多。
驱动类找不到通常是你根本没把jar包放进WEB-INF/lib目录。注意,使用Maven时依赖会打到lib里,手动建的项目如果漏了这一步,运行时会报ClassNotFoundException。这个问题和我在前面提到的clickhouse驱动连接思路一样:不管什么数据库驱动,只要jar包不在类路径上,你Class.forName写得再对也没用。
5.3 jsp改了不生效:多半不是代码的问题
这个话题几乎每个初学JSP的人都搜过。改了JSP刷新页面还是老样子,第一反应是自己的改动有问题,回头检查代码半天才发现改了也没用。
实际原因基本是两个:一是Tomcat没有热部署成功,你改的是工作区或target目录里的文件,但Tomcat已经加载的Class没有更新;二是浏览器缓存了旧页面。我的习惯是:项目直接以war exploded方式部署到Tomcat,IDEA里改完代码不要只点浏览器刷新,要让Tomcat把改动重新加载完,再按Ctrl+F5强刷浏览器。新建或者修改了Servlet之后,千万不要依赖自动热部署,手动重启Tomcat更稳。
5.4 web.xml和注解别重复配置
现在网上很多老教程还是用web.xml方式配置Servlet映射,很多同学就照着写一份xml,然后又看到新教程教用@WebServlet注解,也在代码上加了注解。结果启动Tomcat直接报错,提示Servlet映射冲突。
其实这两种方式是等价的:注解写在类上,就等价于在web.xml里配置了一个servlet-mapping。你只需要选一种。对课设来说,我倾向用@WebServlet,少写一坨XML配置,代码里还能直接看到路径。如果课程要求必须展示web.xml配置的完整示例,那你就把注解去掉,class文件里不要出现@WebServlet,不然重复映射是跑不掉的。
6. 从"能跑"到"拿得出手":课程设计进阶的几个方向
6.1 分页和模糊查询
一个小系统跑通之后,下一步我建议加分页。学生表数据一旦多起来,一页显示几百上千行,体验会非常差,老师也会觉得你欠缺考虑。分页核心SQL就一句话:
SELECT * FROM t_student ORDER BY id DESC LIMIT ?, ?;Servlet里接收pageNum和pageSize参数,计算出offset = (pageNum - 1) * pageSize,查总数算出总页数,然后把当前页数据和分页信息一起给JSP。这些逻辑不难,但会逼着你理解"为什么很多框架里都有PageHelper或者Page对象"——它们做的就是这个事情。
模糊查询也是一样的思路,核心SQL从固定条件变成:
SELECT * FROM t_student WHERE name LIKE ?;注意这里要手动拼上%通配符,用PreparedStatement传参时写成"%" + keyword + "%",而不是把拼接后的SQL直接塞进去。这个区别虽然代码上只差一点,但理解后你就明白防SQL注入是怎么回事了。
6.2 把JSP里的Java代码换成EL和JSTL
我见过很多课设代码里,JSP页面大量使用<% %>脚本片段,把Java逻辑直接写在HTML里。如果你做课设只是应付答辩,这样确实最快。但如果你想拿这个项目去面试或者写进简历,建议花半天时间把页面里的脚本片段全部换成EL表达式和JSTL标签。
EL表达式和JSTL的好处是职责清晰:JSP只负责取值和循环展示,不写任何与业务相关的Java代码。最重要的是,这能让你更容易理解现代前后端分离趋势下"模板引擎"的概念。后面你如果接触Thymeleaf或Vue的模板语法,会发现思路完全是通的。
另外,之前热搜里有"jsp实现数据导出为excel"的需求,这个在小系统里也不难加:在Servlet里从数据库查出List,用POI或者EasyExcel把数据一行行写进Workbook,再通过response.getOutputStream()直接输出给浏览器下载。核心不是写代码,而是理解HTTP响应的Content-Type和Content-Disposition这两个响应头怎么设置,以及为什么下载文件的文件名要做URLEncoder编码。
6.3 把透明JDBC迁移到框架:这些过渡是顺水推舟的
写完这个纯Servlet项目,你再看现在主流的Java Web框架,迁移路径会特别清晰。DAO层的JDBC代码重复性高,数据库操作一多非常臃肿,这就是MyBatis和JdbcTemplate存在的意义——它们把"ResultSet转对象"这件重复劳动封装了。Servlet里手动封装参数的逻辑,被Spring MVC的@RequestParam和对象绑定替代。DBUtil里手动管理连接的过程,被连接池和框架的数据源管理替代。
我个人的体会是,这个项目做完后你真正带走的有两样东西:第一,你亲眼见过一个HTTP请求从浏览器出发,到Servlet、DAO、数据库,再一路返回渲染成HTML页面的完整生命周期;第二,你踩过乱码、404、空指针、重复提交这些坑,知道它们发生的原因。把这两样东西讲清楚,比用什么新技术栈都值钱。
如果时间允许,最后建议你把写好的代码删掉,不看任何参考,自己重新从空目录写一遍。照抄一遍只能让你"看懂",重写一遍才能让你真正记住。等你有一天不需要查资料就能把DBUtil、StudentDao、Servlet、JSP这四层一口气写出来,就可以放心对别人说:Java Web的底子,我算是打牢了。
本文还有配套的精品资源,点击获取