我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

JavaWeb课程设计实战:JSP+Servlet实现个人与家乡信息管理系统

JavaWeb课程设计实战:JSP+Servlet实现个人与家乡信息管理系统 1. 项目缘起与整体设计思路1.1 这个课程设计到底要做什么JavaWeb课程设计几乎是每个计算机相关专业学生绕不开的一道坎。题目看起来简单——“制作一个页面展示和管理自己和家乡的信息”但真正动手做的时候很多人才发现里面涉及的知识点远比想象中密集。这个项目的本质是用Java技术栈搭建一个具备增删改查能力的动态信息管理站点前端负责展示后端负责逻辑处理数据库负责数据持久化。具体来说这个项目需要实现的核心功能包括一个展示页面用来呈现个人信息姓名、专业、年级、兴趣爱好等和家乡信息地理位置、特色美食、风景名胜、民俗文化等一个管理后台允许对上述信息进行新增、修改、删除操作以及前后端之间的数据交互逻辑。听起来像是“做个网页”实际上它串联了HTML、CSS、JavaScript、Servlet、JSP、JDBC、数据库设计、会话管理、请求转发与重定向等一整套JavaWeb核心知识。适合做这个项目的人是已经学完Java基础、正在学习或刚学完Servlet和JSP的在校学生或者想通过一个完整项目把零散知识串起来的自学者。如果你连HttpServletRequest和HttpServletResponse都还没接触过建议先把基础过一遍再上手否则会做得很痛苦。1.2 为什么选择JSPServlet而不是直接上框架这是我在带课程设计时被问得最多的问题“都什么年代了还用JSP直接用Spring Boot不香吗”这个问题值得认真回答。课程设计的核心目的不是让你快速交付一个产品而是让你理解Web开发的底层运作机制。Spring Boot确实能让你少写很多配置但它的高度封装会掩盖很多关键细节——请求是怎么被处理的、会话是怎么维持的、数据库连接是怎么管理的。用JSPServlet做一遍这些环节你都得亲手处理做完之后再看Spring Boot你会明白它到底帮你做了什么。从技术选型角度JSPServlet方案有几个实际优势。第一学习成本可控不需要理解依赖注入、AOP、自动配置这些概念第二调试直观出了问题堆栈信息清晰不像框架那样报错层层嵌套第三部署简单一个Tomcat加上项目war包就能跑起来不需要额外安装Maven以外的构建工具。当然JSPServlet也有明显的缺点比如代码容易写得混乱、视图和逻辑容易耦合但这些恰恰是你可以通过良好的项目结构来规避的而规避的过程本身就是一种学习。我的建议是课程设计用JSPServlet把MVC分层思想贯彻进去做完之后再用Spring Boot重写一遍同样的功能对比两者的差异。这样一轮下来你对JavaWeb的理解会比只做一个Spring Boot项目深刻得多。1.3 项目整体架构怎么划分一个结构清晰的JavaWeb项目应该遵循MVCModel-View-Controller分层原则。具体到这个项目我建议的包结构是这样的com.homepage.entity存放实体类比如PersonalInfo、HometownInfo、User等每个类对应数据库中的一张表。com.homepage.dao数据访问层封装所有JDBC操作提供增删改查方法。这一层只负责和数据库打交道不关心业务逻辑。com.homepage.service业务逻辑层处理具体的业务规则比如“修改信息前先检查是否登录”“删除前确认记录存在”等。com.homepage.servlet控制层接收前端请求调用Service层处理然后转发或重定向到相应页面。com.homepage.util工具类比如数据库连接工具DBUtil、字符串处理工具等。com.homepage.filter过滤器用于处理字符编码、登录验证等全局逻辑。前端页面放在WebContent或webapp目录下JSP文件按功能分目录存放比如/admin/放后台管理页面/user/放前台展示页面。静态资源CSS、JS、图片统一放在/static/目录下。这种分层方式的好处是职责清晰。举个例子如果有一天你要把数据库从MySQL换成其他数据库只需要改DAO层的实现Service层和Servlet层几乎不用动。同样如果前端要改成前后端分离的架构也只需要调整Servlet层的返回格式业务逻辑不受影响。注意很多同学做课程设计时喜欢把JDBC代码直接写在JSP里这是大忌。JSP里写Java代码不仅难以维护而且会让页面逻辑和业务逻辑纠缠不清后期改一个功能要翻好几个文件。坚持分层哪怕一开始觉得麻烦后面会感谢自己。2. 核心功能模块的细节拆解2.1 数据库设计两张核心表怎么建数据库设计是整个项目的地基地基没打好后面写代码会处处别扭。这个项目至少需要两张核心表一张存个人信息一张存家乡信息。如果要做登录功能还需要一张用户表。先看个人信息表personal_info的设计CREATE TABLE personal_info ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 姓名, major VARCHAR(100) COMMENT 专业, grade VARCHAR(20) COMMENT 年级, hobby VARCHAR(255) COMMENT 兴趣爱好, intro TEXT COMMENT 个人简介, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;家乡信息表hometown_info的设计CREATE TABLE hometown_info ( id INT PRIMARY KEY AUTO_INCREMENT, location VARCHAR(100) NOT NULL COMMENT 地理位置, food VARCHAR(255) COMMENT 特色美食, scenery VARCHAR(255) COMMENT 风景名胜, culture TEXT COMMENT 民俗文化, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;用户表user的设计CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个设计决策需要解释。第一为什么用utf8mb4而不是utf8因为utf8在MySQL中最多只支持3字节的字符遇到emoji或者某些特殊汉字会报错utf8mb4才是真正的UTF-8编码。第二为什么加update_time字段这是为了方便追踪记录的最后修改时间在管理后台显示“最后更新时间”会让页面看起来更专业。第三密码字段长度设为100是因为如果后续要做密码加密比如MD5或SHA-256加密后的字符串会比原文长很多。实操心得建表时一定要加ENGINEInnoDB因为InnoDB支持事务和外键而MyISAM不支持。虽然这个项目可能用不到事务但养成习惯没坏处。另外字段的COMMENT一定要写过一个月回头看表结构没有注释你根本想不起来某个字段是干嘛的。2.2 数据库连接工具类的封装JDBC操作的第一步是获取数据库连接。如果每次操作都写一遍DriverManager.getConnection()代码会非常冗余而且数据库配置信息散落在各处改起来容易漏。正确的做法是封装一个DBUtil工具类。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/homepage_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; private static final String USERNAME root; private static final String PASSWORD your_password; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { // 依次关闭资源注意关闭顺序和异常处理 } }这里有几个关键点。第一Class.forName()放在静态代码块中保证驱动只加载一次。第二URL中加了serverTimezoneAsia/Shanghai这是MySQL 8.0之后的必须参数不加会报时区错误。第三characterEncodingutf8mb4确保中文不会乱码。第四关闭资源的方法要处理null判断和异常捕获避免因为一个资源关闭失败导致后面的资源无法关闭。如果你想让项目更规范可以把数据库配置信息抽到db.properties文件中用Properties类读取。这样部署到不同环境时只需要改配置文件不用重新编译代码。2.3 实体类与DAO层的对应关系实体类是数据库表在Java中的映射。以PersonalInfo为例public class PersonalInfo { private Integer id; private String name; private String major; private String grade; private String hobby; private String intro; private Date updateTime; // 省略getter和setter方法 }实体类的字段名建议和数据库字段名保持一致或者用驼峰命名对应下划线命名这样在DAO层做结果集映射时不容易出错。字段类型的选择也要注意数据库的INT对应Integer而不是int因为Integer可以为null能更好地处理数据库中的NULL值数据库的DATETIME对应java.util.Date或java.sql.Timestamp。DAO层为每个实体类提供一个对应的DAO类比如PersonalInfoDao里面包含以下方法findAll()查询所有个人信息记录findById(int id)根据ID查询单条记录insert(PersonalInfo info)新增记录update(PersonalInfo info)修改记录delete(int id)删除记录每个方法的实现都遵循“获取连接→创建PreparedStatement→设置参数→执行SQL→处理结果→关闭资源”的流程。这里一定要用PreparedStatement而不是Statement因为PreparedStatement能防止SQL注入而且预编译机制能提升执行效率。public boolean update(PersonalInfo info) { String sql UPDATE personal_info SET name?, major?, grade?, hobby?, intro? WHERE id?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, info.getName()); ps.setString(2, info.getMajor()); ps.setString(3, info.getGrade()); ps.setString(4, info.getHobby()); ps.setString(5, info.getIntro()); ps.setInt(6, info.getId()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }这里用了Java 7的try-with-resources语法能自动关闭Connection和PreparedStatement代码更简洁。注意ResultSet也需要关闭但在executeUpdate()场景下不需要处理结果集。2.4 前端页面的结构设计前端部分虽然不需要像专业前端那样精细但基本的布局和交互还是要有的。展示页面建议分为两个区域个人信息区和家乡信息区。个人信息区用卡片式布局展示姓名、专业、年级、兴趣爱好等家乡信息区可以用图文混排的方式展示地理位置、美食、风景和文化。页面结构用HTML5语义化标签div classcontainer header h1个人与家乡信息展示/h1 /header section classpersonal-info h2个人信息/h2 div classinfo-card pstrong姓名/strong${personalInfo.name}/p pstrong专业/strong${personalInfo.major}/p !-- 其他字段 -- /div /section section classhometown-info h2家乡信息/h2 div classinfo-card pstrong地理位置/strong${hometownInfo.location}/p !-- 其他字段 -- /div /section /divJSP页面中用EL表达式${}来显示数据比传统的% %脚本片段更简洁也更符合MVC的设计理念。如果EL表达式不生效检查一下是否引入了JSTL标签库以及web.xml中的版本声明是否正确。CSS部分不需要太复杂重点是布局清晰、配色舒适。可以用Flexbox做响应式布局保证在手机和电脑上都能正常显示。颜色建议用柔和的色调比如浅灰背景配白色卡片文字用深灰色而不是纯黑色看起来更舒服。注意事项JSP页面中的中文乱码问题几乎每个人都会遇到。解决办法是在每个JSP页面顶部加上% page contentTypetext/html;charsetUTF-8 languagejava %同时在Servlet中设置request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。如果用了POST请求还有乱码检查一下Tomcat的server.xml中Connector的URIEncoding属性。3. 完整实操流程与关键环节实现3.1 开发环境搭建与项目初始化动手写代码之前先把环境搭好。需要准备的东西包括JDK建议1.8或以上、Tomcat建议8.5或9.0、MySQL建议5.7或8.0、IDEEclipse或IntelliJ IDEA都行、MySQL驱动jar包mysql-connector-java。在IDE中创建项目时选择“Dynamic Web Project”Eclipse或“Java Enterprise”项目IDEA。创建完成后把MySQL驱动jar包放到WEB-INF/lib目录下这样Tomcat运行时能自动加载。如果你用Maven管理依赖在pom.xml中添加dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependencyservlet-api的scope设为provided因为Tomcat本身已经包含了这个jar包打包时不需要重复包含否则会冲突。项目创建好后先写一个简单的测试Servlet部署到Tomcat上访问一下确认环境没问题再继续。这一步看似多余但能帮你提前发现端口占用、版本不兼容等问题避免写到一半才发现环境有问题。3.2 展示页面的数据查询与渲染流程展示页面的数据流向是这样的用户访问展示页面 → Servlet接收请求 → 调用Service层 → Service调用DAO层查询数据库 → 返回实体对象 → Servlet将对象存入request域 → 转发到JSP页面 → JSP用EL表达式渲染数据。对应的Servlet代码WebServlet(/showInfo) public class ShowInfoServlet extends HttpServlet { private PersonalInfoService personalService new PersonalInfoService(); private HometownInfoService hometownService new HometownInfoService(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); PersonalInfo personalInfo personalService.getLatest(); HometownInfo hometownInfo hometownService.getLatest(); request.setAttribute(personalInfo, personalInfo); request.setAttribute(hometownInfo, hometownInfo); request.getRequestDispatcher(/user/show.jsp).forward(request, response); } }这里用getLatest()而不是findAll()是因为展示页面通常只需要显示最新的一条记录。如果数据库中有多条记录可以在Service层做逻辑判断比如按update_time倒序取第一条。JSP页面中渲染数据% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head title个人与家乡信息/title link relstylesheet href${pageContext.request.contextPath}/static/css/style.css /head body div classcontainer c:if test${not empty personalInfo} section classpersonal-info h2个人信息/h2 p姓名${personalInfo.name}/p p专业${personalInfo.major}/p p年级${personalInfo.grade}/p p兴趣爱好${personalInfo.hobby}/p p个人简介${personalInfo.intro}/p /section /c:if c:if test${empty personalInfo} p暂无个人信息请先到后台添加。/p /c:if /div /body /html用c:if做空值判断是必要的否则如果数据库中没有数据页面会显示一片空白或者报错。${pageContext.request.contextPath}用来获取项目的上下文路径这样即使项目部署的路径变了静态资源的引用也不会失效。3.3 后台管理功能的增删改查实现后台管理是这个项目的重头戏也是最能体现技术含量的部分。管理功能包括查看所有记录列表、新增记录、编辑记录、删除记录。列表页面的实现比较简单查询所有记录后转发到JSP用c:forEach遍历显示table thead tr thID/th th姓名/th th专业/th th操作/th /tr /thead tbody c:forEach items${personalInfoList} varinfo tr td${info.id}/td td${info.name}/td td${info.major}/td td a href${pageContext.request.contextPath}/admin/editPersonal?id${info.id}编辑/a a hrefjavascript:void(0) onclickconfirmDelete(${info.id})删除/a /td /tr /c:forEach /tbody /table删除操作建议用JavaScript做二次确认避免误删function confirmDelete(id) { if (confirm(确定要删除这条记录吗删除后不可恢复。)) { window.location.href ${pageContext.request.contextPath}/admin/deletePersonal?id id; } }新增和编辑可以共用一个表单页面。新增时表单为空编辑时表单回填原有数据。判断依据是请求中是否携带了id参数。Servlet中这样处理WebServlet(/admin/editPersonal) public class EditPersonalServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String idStr request.getParameter(id); if (idStr ! null !idStr.isEmpty()) { int id Integer.parseInt(idStr); PersonalInfo info personalService.findById(id); request.setAttribute(personalInfo, info); } request.getRequestDispatcher(/admin/personalForm.jsp).forward(request, response); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); // 收集表单参数构建PersonalInfo对象 // 判断id是否为空决定是insert还是update // 操作完成后重定向到列表页面 } }这里有一个重要的设计原则查询用转发修改用重定向。因为如果修改操作后直接转发到列表页面用户刷新页面时会重复提交表单导致数据重复插入。用重定向可以避免这个问题浏览器地址栏会变成列表页面的URL刷新也不会重复提交。3.4 登录验证与Session管理后台管理功能不应该让所有人都能访问所以需要加一个简单的登录验证。用户表里存一个管理员账号登录成功后把用户信息存入Session后续访问后台页面时检查Session中是否有用户信息。登录Servlet的核心逻辑WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(currentUser, user); session.setMaxInactiveInterval(30 * 60); // 30分钟超时 response.sendRedirect(request.getContextPath() /admin/list); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }然后用一个Filter拦截所有/admin/*的请求检查SessionWebFilter(/admin/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }request.getSession(false)表示如果Session不存在就返回null而不是新建一个。这个细节很重要否则每个未登录的请求都会创建一个新Session浪费服务器资源。实操心得密码存储千万不要用明文。哪怕课程设计不要求也建议用MD5或SHA-256做一次哈希。Java中可以用MessageDigest类实现public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }这样即使数据库被看到密码也不是明文。当然MD5本身也不够安全但比明文强得多而且实现简单适合课程设计的场景。4. 常见问题排查与避坑经验4.1 中文乱码问题的完整解决方案中文乱码是JavaWeb开发中最常见的问题没有之一。乱码可能出现在三个环节请求参数乱码、响应内容乱码、数据库存储乱码。每个环节的解决方案不同需要逐一排查。请求参数乱码POST请求的参数乱码用request.setCharacterEncoding(UTF-8)解决这行代码必须放在获取任何参数之前。GET请求的参数乱码需要修改Tomcat的server.xml在Connector标签中添加URIEncodingUTF-8。注意Tomcat 8.0之后默认就是UTF-8但如果你用的老版本这个配置是必须的。响应内容乱码用response.setContentType(text/html;charsetUTF-8)解决。如果页面还是乱码检查JSP顶部的page指令是否设置了charsetUTF-8。数据库存储乱码检查数据库和表的字符集是否为utf8mb4连接URL中是否加了characterEncodingutf8mb4。如果数据已经乱码了需要清空表重新插入。排查乱码问题的思路是先确定乱码发生在哪个环节。可以在Servlet中打印接收到的参数值如果打印出来就是乱码说明是请求环节的问题如果打印正常但页面显示乱码说明是响应环节的问题如果页面显示正常但数据库里是乱码说明是数据库环节的问题。4.2 数据库连接失败的排查步骤数据库连接失败通常有以下几种原因按排查优先级排列问题现象可能原因解决方案ClassNotFoundException驱动jar包未引入将mysql-connector-java放入WEB-INF/libAccess denied for user用户名或密码错误检查DBUtil中的用户名密码Unknown database数据库不存在创建对应的数据库Communications link failureMySQL服务未启动启动MySQL服务The server time zone value...时区未设置URL中添加serverTimezone参数Public Key Retrieval is not allowedMySQL 8.0安全设置URL中添加allowPublicKeyRetrievaltrue我遇到过最坑的一个问题是项目在本地跑得好好的换一台电脑就报连接失败。排查了半天发现是那台电脑的MySQL端口不是默认的3306而是3307。所以排查连接问题时先确认端口号、用户名、密码、数据库名这四个基本信息是否正确。4.3 JSP页面报错的常见原因JSP报错信息往往很长但核心信息通常在最后几行。常见的JSP报错包括500错误提示“Unable to compile class for JSP”通常是JSP中的Java代码有语法错误比如少了一个分号、括号不匹配等。仔细看错误信息中提示的行号去对应位置检查。404错误提示“The requested resource is not available”检查Servlet的WebServlet注解路径是否正确或者web.xml中的url-pattern是否匹配。另外注意项目部署路径如果项目名是homepage访问路径应该是http://localhost:8080/homepage/showInfo而不是http://localhost:8080/showInfo。EL表达式不生效页面直接显示${personalInfo.name}这是因为EL表达式被当成了普通文本。解决办法是在JSP顶部加上% page isELIgnoredfalse %或者检查web.xml的版本声明是否为Servlet 2.4以上。JSTL标签报错“The absolute uri cannot be resolved”缺少JSTL的jar包需要引入jstl.jar和standard.jar或者Maven中的jstl依赖。4.4 部署到Tomcat时的注意事项项目在IDE中运行正常部署到独立的Tomcat上却出问题这种情况也很常见。主要检查以下几点第一确认war包中包含了所有依赖的jar。IDE中运行时IDE会自动把依赖加到classpath中但打包时可能遗漏。检查WEB-INF/lib目录下是否有所有需要的jar包。第二确认Tomcat的版本和Servlet版本匹配。如果你在web.xml中声明了Servlet 4.0但Tomcat 7只支持到Servlet 3.0就会报错。要么升级Tomcat要么降低web.xml的版本声明。第三确认数据库配置是否正确。部署到服务器上时数据库可能不在本地需要修改连接URL中的IP地址。第四查看Tomcat的日志文件。日志在logs目录下catalina.out或localhost.log中会有详细的错误信息。很多人遇到问题只看浏览器上的报错其实Tomcat日志里的信息更详细。避坑技巧部署前先在本地用mvn clean package如果用了Maven打一个war包解压检查WEB-INF/lib和WEB-INF/classes目录是否完整。这个习惯能帮你提前发现很多部署问题。4.5 代码组织与后期维护的建议课程设计做完之后很多人就不管了。但如果你想让这个项目成为简历上的亮点或者作为后续项目的模板代码组织就很重要。首先命名要规范。类名用大驼峰PersonalInfoService方法名和变量名用小驼峰findById、userName常量全大写加下划线MAX_PAGE_SIZE。不要用拼音命名也不要用aaa、bbb这种无意义的名称。其次注释要写关键位置。不需要每行都写注释但类的作用、方法的参数和返回值、复杂的业务逻辑一定要有注释。特别是DAO层的SQL语句注释一下每个参数对应什么条件后期改起来会方便很多。第三把可配置的信息抽出来。数据库连接信息、每页显示的记录数、Session超时时间等都应该放在配置文件或常量类中而不是硬编码在代码里。第四养成用Git的习惯。哪怕只有自己一个人开发用Git管理代码也能让你随时回退到之前的版本。每次完成一个功能就提交一次提交信息写清楚做了什么。这个项目虽然简单但如果你能把上面这些细节都做到位它就不只是一个课程设计而是一个能体现你工程素养的作品。我在实际带项目时发现那些在课程设计阶段就注重代码质量的同学到了毕业设计和工作阶段上手速度明显快很多。
返回列表