简介:这套基于 Java(JSP)+ MySQL 的课程设计资源,是一个覆盖游客浏览、注册留言到管理员后台维护的完整个人与家乡展示管理平台,适合 Java Web 初学者、课程设计或毕业设计学生作为参考和二次开发基础。前端包含欢迎页照片轮播、个人简介、个人技能分类下拉选择、家乡简介、美食美景风土人情特色介绍以及支持回复的留言板;后端则提供用户管理、技能管理、家乡特色管理和留言管理,管理员可在 Web 界面完成增删改查。资源共 2000 个文件,压缩包 29.69MB,主要文件包括 48 个 JSP 页面、40 个 Java 类、82 个 JS 脚本、38 个 CSS 样式、1621 个 SVG 图标以及 SQL 初始化脚本,页面与后台交互路径完整。后台界面基于 SB Admin 2 管理模板并配套 FontAwesome 图标资源,目录结构清晰,便于对照学习登录权限、留言回复、数据表设计与增删改查实现。目前已有 288 人学习下载,适合希望基于完整项目快速上手 JSP+Servlet+MySQL 开发流程的读者。
1. 一个 JSP + MySQL 的课程设计选题,为什么值得认真做完
做 Java 方向课程设计和毕设的同学,大多已经把“传统 JavaWeb 项目”当成过时产物,上来就想套 Spring Boot。但“基于Java(JSP)+MySQL实现个人与家乡展示管理平台”这类题目,恰恰是面试官最常追问的那类项目:没有框架替你挡掉细节,请求怎么进来、连接怎么打开、SQL 怎么拼、数据怎么渲染,全得自己动手。做完它,你对 Java Web 的底层链路会有一个完整概念,而不是只会在 application.yml 里改端口。这个平台同时包含“对外展示”和“后台管理”两条线,能用到的技术点很全:JSP 页面渲染、Servlet 接收参数、JDBC 操作 MySQL、Session 会话控制、文件上传与回显。适合用来做课程设计、毕设起点、或者简历上的第一个完整 Java Web 实践项目。
2. 先把平台拆成三条线:数据模型、目录结构与功能边界
2.1 平台不只是一个“网页”,而是展示区 + 管理区两个视图
一个个人与家乡展示管理平台,如果只做静态 HTML 展示,实际上用不到 JSP 和 MySQL。加上“管理”两个字,项目才真正有了后端的价值。我一般会把这类平台拆成两个视图:面向游客的展示区,和面向管理员的后台管理区。展示区包含个人简介、家乡介绍、图片展示、以及留言或联系信息;管理区则负责登录、维护个人资料、上传本地图片和更新家乡信息。两块共用同一套数据表,只是通过不同路径的 Servlet 和 JSP 来访问。
这种拆分的好处是:功能边界清晰,写代码的时候不会把展示逻辑和写操作糊在一起。新手常见的翻车姿势是把增删改写的 JSP 文件直接放在 web 根目录下,游客也能看到管理入口;更稳妥的做法是分成/web下的普通页面和一个/admin路径,配合后面要讲的登录过滤器做访问控制。管理功能不一定要多,登录、修改、上传这三项就能覆盖课程设计的大部分评分点。
2.2 建三张表就够:用户表、家乡信息表、图片资源表
数据库设计是这个项目里最不能跳过的部分。很多人一上来就建七八张表,字段多到自己都记不住,最后 SQL 联表查得头疼。按这个平台的业务规模,我常用的做法是三张核心表:用户表存管理员账号;家乡信息表存家乡名称、简介、地理标签、美食标签;图片资源表存图片路径和图片属于哪条信息。下面是建表 SQL,可以直接在 MySQL 里执行:
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE hometown_info ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, summary TEXT, content TEXT, tag VARCHAR(50), category VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE image_resource ( id INT PRIMARY KEY AUTO_INCREMENT, info_id INT NOT NULL, img_path VARCHAR(255) NOT NULL, img_desc VARCHAR(255), upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_info FOREIGN KEY (info_id) REFERENCES hometown_info(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;字符集必须用utf8mb4,否则 MySQL 5.7 之前的默认utf8存不了 emoji 和部分生僻字;外键约束在课程设计里建议保留,让评分的人看到你有意识地维护数据完整性。密码字段不要存明文,至少用 MD5 或 SHA-256 做一层哈希,这个细节经常被作为安全加分点。三张表之间的逻辑是:hometown_info记录一条家乡信息,image_resource通过info_id挂到某条信息下面,一对多。
2.3 在 IDEA 里新建传统 JSP 项目的目录结构
现在用 IDEA 新建 JSP 项目,和早期 Eclipse 时代略有不同,但目录本质还是 Java Web 的标准结构。在 IDEA 里选择 New Project,注意不要选 Spring Initializr,而是选 Jakarta EE 或 Java Enterprise 分类下的 Web Application,也可以直接创建普通 Java 项目后手动添加 Web 支持。我一般习惯手动搭建,这样对目录结构才真正有感觉:
hometown-platform/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/platform/ │ │ │ ├── servlet/ │ │ │ ├── dao/ │ │ │ └── util/ │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ ├── web.xml │ │ │ └── lib/ │ │ ├── index.jsp │ │ ├── hometown.jsp │ │ └── admin/ │ │ ├── login.jsp │ │ └── edit.jspWEB-INF是安全边界,放进去的 JSP 不能通过浏览器直接访问,必须由 Servlet 转发;lib目录放 MySQL 驱动和 JSTL 标签库的 jar 包;其余 JSP 是入口页面。新手在 IDEA 里建 JSP 项目时最容易卡住的是没有把webapp目录标记为 Web 资源目录,导致 run 的时候找不到页面。在 Project Structure 里确认 Web Resource Directory 指向src/main/webapp,再配置一个 Tomcat 就可以启动了。
3. 用 JDBC 和 JSP 把“展示”做出来:从数据库到页面的完整链路
3.1 写一个 JDBC 连接工具类,别在每个 Servlet 里重复连接
展示功能的第一步是让页面读到 MySQL 数据。很多人会不假思索地在每个 Servlet 里写DriverManager.getConnection(),这种做法在小项目里能跑,但连接每次都要经过 TCP 握手和 MySQL 认证,页面稍多就会变慢。课程设计虽不限性能,但代码评审时会看你能不能把公共逻辑抽出来。我一般会写一个DBUtil工具类,负责加载驱动和获取连接:
public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/hometown_db" + "?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; 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, USER, PASSWORD); } }连接 URL 里的serverTimezone=Asia/Shanghai是 MySQL 8 的强制要求,不加会报时区错误;characterEncoding=utf8保证中文参数和查询结果不乱码。MySQL 8 用com.mysql.cj.jdbc.Driver,MySQL 5.x 用com.mysql.jdbc.Driver,这是新手最容易踩红的点。这个工具类不负责连接的关闭,关闭动作放在 DAO 层的finally块里做。
3.2 用 Servlet 查数据并按需转发,JSP 只负责渲染
展示页面的经典链路是:浏览器请求/hometown→ Servlet 调 DAO 查询hometown_info表 → 把 List 放进 request → 转发到hometown.jsp。这一层分离是 JSP 项目里基本的 MVC 分工,不会导致 JSP 页面里写大量 Java 代码。下面是一个最简 Servlet:
@WebServlet("/hometown") public class HometownServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HometownDao dao = new HometownDao(); List<HometownInfo> list = dao.findAll(); request.setAttribute("list", list); request.getRequestDispatcher("/hometown.jsp").forward(request, response); } }@WebServlet注解是 Servlet 3.0 以后的功能,省去了在 web.xml 里写映射的麻烦;forward是服务器内部跳转,浏览器 URL 不变,数据通过 request 属性传递。这里有个细节:不能直接sendRedirect,因为重定向会丢失 request 里的数据。接下来在hometown.jsp里用 JSTL 渲染列表:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <body> <h2>我的家乡</h2> <c:forEach var="item" items="${list}"> <div class="card"> <h3>${item.title}</h3> <p>${item.summary}</p> <span>标签:${item.tag}</span> <a href="hometownDetail?id=${item.id}">查看详情</a> </div> </c:forEach> </body> </html>JSTL 的c:forEach替代了<% for(...) {} %>这种 scriptlet 写法,页面维护起来干净得多。${item.id}调用的是HometownInfo对象对应字段的 getter,所以我一般在实体类里写驼峰命名的属性,例如createTime映射数据库的create_time,getter 写成getCreateTime()。如果列表查不出来,先看控制台有没有 SQL 异常,再确认list是否真正放进了 request——熟悉的人一眼就能查完,新手经常卡在 null 上。
3.3 详情页的图片显示:只存路径,不存文件本体
详情页要展示家乡风景图片,这里有一个新手特别容易踩的坑:把图片转成 Base64 字符串直接存进数据库。这种做法在小数据量下能显示,但图片稍大就会让查询变得很慢,而且 JSP 的<img>标签也会因为超长字符串而变得难以处理。常见的可靠做法是把图片文件上传到服务器的某个磁盘目录,数据库只存相对路径,页面通过相对路径访问。
我会在项目根目录下建一个uploads目录,图片上传后用 UUID 重命名,用getRealPath获取真实磁盘路径来保存。访问图片时的 URL 由 Tomcat 映射,可以直接在 Server 配置里加一段资源映射,我的做法是让uploads目录和项目部署目录同级,再用 HTTP 路径访问:
<Context docBase="/data/hometown-platform/uploads" path="/uploads" />代码层面就一句话:request.getServletContext().getRealPath("/uploads")拿到物理路径,然后拼接文件名。这条链路里最容易翻车的不是保存,而是部署包重新打过后旧图片丢失——如果你用getRealPath保存,文件落在 Tomcat 的部署目录里,重新部署会被清掉;所以实际课程设计中,图片保存路径要么写到外部磁盘,要么在文档里明确说明“重新部署前需要备份 uploads 目录”。很多同学的图片在答辩演示前一天突然全部 404,多半就是这个原因。
4. 管理端登录与增删改:会话、过滤器和表单提交的完整实现
4.1 登录与 Session 会话保持:用过滤器拦住未登录请求
管理区必须先登录。登录逻辑很经典:表单提交用户名和密码到LoginServlet,Servlet 从user表查记录,如果密码匹配,就把用户 id 写进session.setAttribute("admin", ...),然后重定向到后台首页;不匹配就返回登录页并带一个错误提示。这里的关键点是密码比对应该在 Java 端做,不要拼在 SQL 里,否则团队里讨论时容易被指出存在 SQL 注入风险。SQL 采用预编译写法:
String sql = "SELECT * FROM user WHERE username=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ResultSet rs = ps.executeQuery(); if (rs.next()) { String hashed = rs.getString("password"); if (hashed.equals(md5(password))) { session.setAttribute("admin", rs.getInt("id")); response.sendRedirect("admin/index.jsp"); } }PreparedStatement的?占位符能防止闭合 SQL;所有需要登录才能访问的路径,用一个 Filter 统一拦:
@WebFilter("/admin/*") public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpSession session = request.getSession(false); Object admin = (session != null) ? session.getAttribute("admin") : null; if (admin == null) { ((HttpServletResponse) res).sendRedirect(request.getContextPath() + "/admin/login.jsp"); } else { chain.doFilter(req, res); } } }getSession(false)不会自动创建新会话,避免未登录用户访问时产生无意义的 session。过滤器的目标路径admin/*把整个后台文件夹保护起来,不在这个路径下的管理 Servlet 要特别注意路径设计。登录页面本身要放在/admin/login.jsp,因为如果放在不受保护的位置,任何人都可以直接打开。
4.2 信息的新增与修改:一个 Servlet 处理两类操作的惯用写法
后台管理家乡信息,最常见的管理操作是新增和更新。可以让一个EditServlet同时接收新增和更新的请求,用表单里隐藏的id字段判断:有 id 就是更新,没有就是新增。这样做的好处是减少 Servlet 数量,逻辑清晰。JSP 里的表单写法很简单:
<form action="${pageContext.request.contextPath}/admin/edit" method="post"> <input type="hidden" name="id" value="${info.id}"> <input type="text" name="title" value="${info.title}"> <textarea name="content">${info.content}</textarea> <button type="submit">保存</button> </form>Servlet 端先统一取参数,再根据 id 是否存在决定调insert还是update方法。这里要特别注意 JSP 表达式转义:${info.content}是文本域的值,如果内容里有</textarea>或引号,页面会破相甚至被脚本注入;稳妥做法是用 JSTL 的c:out标签输出,它可以默认做 HTML 转义。我一般把这一步视为“上线前必须检查”的项目,直接写<textarea>${...}</textarea>在展示用户可编辑的富文本时并不安全。
删除操作实现更直接,但一定要做确认提示。我使用的删除链接不是直接跳到 Servlet,而是先在管理列表页弹一个 JavaScript 的确认框,确认后再提交。删除请求建议用 POST 而不是 GET,因为搜索引擎爬虫和浏览器预取可能会导致意外的数据丢失,而且课程设计答辩时也更容易解释清楚你考虑了“幂等与安全”这类问题。
4.3 表单乱码的根源:POST 请求体编码和 Tomcat 配置
JSP 项目里最常见的“翻车现场”就是后台新增一条家乡信息,存进 MySQL 再查出来,中文全变成了“?”。这个问题几乎每次都有,原因是 POST 请求体的编码默认是 ISO-8859-1,而 JSP 页面声明的编码是 UTF-8。常见做法是写一个编码过滤器,统一把请求和响应的编码设为 UTF-8:
@WebFilter("/*") public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; request.setCharacterEncoding("UTF-8"); res.setCharacterEncoding("UTF-8"); chain.doFilter(req, res); } }这个 Filter 要注册在其它 Filter 之前,因为getParameter一旦被调用,编码就定型了。Tomcat 8 及以上版本对 GET 请求的 URI 编码默认是 UTF-8,旧版 Tomcat 需要在server.xml的 Connector 上加URIEncoding="UTF-8"。如果依然乱码,直接从三个位置排查:MySQL 连接 URL 是否带characterEncoding=utf8、数据库表是否为utf8mb4、JSP 第一行contentType是否设置为UTF-8。记住一个经验:乱码是在“请求 → 应用 → 数据库 → 页面”这条链上任何一个环节丢了编码,只用一处设置往往不够。
5. JSP + MySQL 常见问题排查:五个必踩的坑
5.1 Tomcat 部署后页面全是问号或白屏
现象:本地 IDEA 运行正常,把 war 包丢到 Tomcat 的 webapps 下,重启后页面中文全是问号,甚至有些页面直接白屏。
原因:IDEA 里运行 Tomcat 时,JVM 默认文件编码通常跟随系统;而独立部署的 Tomcat 在 Linux 上默认用 UTF-8,在 Windows 老版本里可能用 GBK,导致 JSP 编译时字节流读取错误。白屏则常是缺少 JSTL jar 包,Tomcat 自带 lib 不包含 JSTL 依赖。
解决:每个 JSP 文件第一行写完整的page指令;把jstl.jar和standard.jar放进WEB-INF/lib。Linux 上可以检查一下tomcat/conf/server.xml中 Connector 是否加URIEncoding="UTF-8"。我自己的习惯是不依赖 IDE 里“运行”按钮能过就算过,每次提交前手动把 war 复制到本地 Tomcat 跑一遍,页面源码里看中文字节,这一步能提前发现大多数编码问题。
5.2 连接 MySQL 8 报 SSL 错误或时区错误
现象:点击页面时后台报SSLHandshakeException或者The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。
原因:MySQL 8 默认开启了 SSL 加密,且时区规则变了;驱动版本和 MySQL 版本不匹配时,旧的连接代码直接不认。
解决:连接 URL 里加参数:
jdbc:mysql://localhost:3306/hometown_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueuseSSL=false是本机开发和课程设计环境里最简单有效的关闭方式;allowPublicKeyRetrieval=true解决 MySQL 8 在非 SSL 模式下缓存公钥的报错。如果有强迫症,可以给 MySQL 配置时区而不是靠 URL 参数,但相对于学生项目而言,URL 参数是最低成本的“后悔药”。
5.3 JDBC 驱动加载不到或 ClassNotFoundException
现象:控制台报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,但mysql-connector-javajar 明明在项目的 External Libraries 里。
原因:IDEA 下 jar 包必须在部署结构中打进去。很多人只把 jar 加到了项目的 classpath,没有把它加入WEB-INF/lib或者 Artifact 的 Output Layout。
解决:Project Structure → Artifacts → 选中项目 war → 在 Output Layout 里检查是否有WEB-INF/lib,把 mysql 驱动和 JSTL 的 jar 通过右边的 “Put into Output Root” 放进去。验证方式是看编译输出目录下WEB-INF/lib/里的文件,用jar tf mysql-connector-java-8.x.jar看驱动类是否真实存在。
5.4 图片上传后刷新页面 404
现象:上传图片后第一次能显示,刷新一下就裂图了,或者重新部署后所有图片消失。
原因:图片保存路径写进了部署目录内的uploads文件夹。Tomcat redeploy 时会清理旧目录,上传的文件随之被清掉;还有一种情况是保存时用了相对路径“uploads”,但当前工作目录和 Tomcat 的 bin 目录不一致,导致图片写到了别的磁盘位置。
解决:上传的磁盘路径不要挂在项目里。常见做法是在服务器上建一个固定目录,比如 Windows 下D:/hometown_uploads,Linux 下/data/hometown_uploads,在代码中通过配置文件读取路径。数据库里存的是带 URI 的访问路径,例如/uploads/20250101_xxx.jpg,然后把 Tomcat 的 Context 映射到那个磁盘目录。这条经验是我自己吃过亏后才固化的习惯。
5.5 JSP 页面报错信息不可读,项目直接 500
现象:后台管理页面出现异常后,Tomcat 返回一个冗长的 500 页面,定位不到是哪一行代码出的问题。
原因:标准错误输出被 Tomcat 默认页面吞掉了,或者项目里把stackTrace打印到了控制台而不是页面日志。
解决:开发阶段不要依赖错误页,直接在doGet/doPost里打印完整堆栈:
e.printStackTrace();然后在 IDEA 的 Console 里按异常栈逐行移动。进阶策略是给项目加一个简单的错误处理 Servlet,把异常信息记录到文件或数据库。课程设计阶段不建议做复杂日志框架,一个/logs目录下按天写的文本文件足够用。定位 JSP 页面报错时还有个技巧:JSP 编译后的 Java 文件路径会打印在异常信息里,可以找到 target 下对应的源码直接看编译产物。
6. 从“能跑”到“能演示”:分页、校验与打包部署的最后一公里
6.1 给展示列表加分页:10 条一页,SQL 用 LIMIT 实现
个人与家乡展示管理平台数据量不大,但答辩评分项里常有“是否支持分页展示”这种加分点。常见的做法是给HometownServlet加上page参数,DAO 里用 LIMIT 查询,页面上放上一页和下一页链接。SQL 写法:
SELECT * FROM hometown_info ORDER BY create_time DESC LIMIT ?, ?;参数分别是偏移量和每页条数。计算偏移量时注意页码从 1 开始,偏移量是(page-1) * pageSize。这个实现不要用LIMIT加逗号的形式拼 SQL 字符串,仍然用PreparedStatement。分页时 URL 上要保留查询参数,例如hometown?page=2,避免切页后丢失条件。
6.2 给管理表单加前端校验,但后端不能只信任校验
后台新增家乡信息时,表单里的标题、摘要、标签都可能为空。我一般会先在 JSP 页面里写一段 JavaScript 做前置提示,阻止空表单提交,但这只是体验层面的优化;真正判空要在 Servlet 端再做一次,否则绕过页面的请求会把脏数据直接写进 MySQL。后端校验可以简化成如下片段:
if (title == null || title.trim().isEmpty()) { request.setAttribute("error", "标题不能为空"); request.getRequestDispatcher("/admin/edit.jsp").forward(request, response); return; }这里用trim()过滤了纯空格输入。校验失败时返回编辑页并保留用户已填内容——做法是将请求参数重新放回 request 属性,JSP 里再用${param.title}回填。这个小细节比弹一个“提交失败”更有演示价值。
6.3 打包 war 部署:从 IDEA 的 Artifact 到外部 Tomcat
传统 JSP 项目最终要打成 war 包部署到 Tomcat。在 IDEA 里选择 Build → Build Artifacts → 选择hometown-platform:war,打包后把 war 复制到 Tomcat 的webapps目录下启动即可。需要注意的是:如果 Tomcat 已经存在同名目录,停止后删掉旧目录再重新放 war,否则会解压出旧文件覆盖新内容,这种现象经常被误认为“代码没改生效”。部署完成后访问地址是:
http://localhost:8080/hometown-platform/如果 404,先看 Tomcat logs 里有没有 report 或解压记录;如果首页能开但样式丢失,大概率是页面里的静态资源路径没有加pageContext.request.contextPath。这个部署流程值得在答辩前完整走三遍,尤其是清理旧目录这一步,是我自己几次演示翻车后养成的习惯。另外,上传图片的外部磁盘目录、数据库连接参数,在部署到另一台机器时要同步改,我建议把连接参数单独放到一个config.properties里,避免每次换环境都改 Java 源码重新编译。做成一个小工具类读配置,是这类 JSP 项目性价比最高的收尾改进,希望帮到你。
本文还有配套的精品资源,点击获取