基于Servlet+Jsp+MySQL的艺术作品展览系统源码解析与部署指南
简介一套基于ServletJspMySQL构建的艺术作品展览管理系统源代码与数据库文件一并提供面向Java Web初中级学习者及需要快速落地展览管理功能的开发者。系统设计了艺术作品、展览信息、用户信息三张核心数据表支持注册登录、展览维护、作品分类检索与在线交流完整覆盖数字化展览管理的基本业务链路。资源包共50个文件包括18个java后台逻辑、15个jsp页面、12个xml配置、1个sql数据库脚本及说明文档压缩包仅52KB目录按Maven工程结构组织配合README可快速了解项目构成。通过源码可深入理解JSP转Servlet的运作机制、JDBC连接MySQL的写法以及分层代码的组织方式数据库脚本可直接初始化便于本地运行和二次开发。已有52人学习对课程设计、毕业设计或艺术展览线上化实践均具参考价值。1. 一套“老技术栈”的毕设系统为什么今天还值得照着跑一遍如果你正在找一个能直接运行、能讲清楚、改起来不费劲的 JavaWeb 项目“基于ServletJspMySQL的艺术作品展览管理系统源代码数据库”几乎是搜索“基于jsp的毕设选题”时躲不开的名字。这类系统做的事情不复杂管理员登录后上传作品、按分类维护、决定上架还是下架访客在前台浏览作品列表和详情所有业务数据落在 MySQL 里Servlet 负责接收请求和控制跳转Jsp 负责把数据渲染成页面。放在今天看ServletJsp 这套栈没有 Spring Boot 的自动配置和注解魔法但反过来说它也没有黑匣子配合完整源代码和 SQL 脚本新手能在一台干净电脑上从建库开始一步步跑通熟手也能快速定位业务逻辑改需求。这篇文章就按我常用的落地顺序把这个方案拆成从源码理解到部署、建库、排错、验证的完整路径。2. 源码里没有“框架魔法”Servlet、Jsp 与三层结构怎么分工2.1 从 war 项目的目录结构读懂第一件事拿到这种源代码先别急着打开某个 Java 文件而是看整个项目的目录结构。传统 ServletJsp 项目的分包方式很固定基本长这样项目根目录/ ├── src/ │ ├── com.xxx.controller # Servlet负责接收请求和页面跳转 │ │ ├── AdminLoginServlet.java │ │ ├── ArtworkListServlet.java │ │ └── ArtworkAddServlet.java │ ├── com.xxx.service # 业务层处理上传、上下架、统计等规则 │ │ ├── ArtworkService.java │ │ └── ArtworkServiceImpl.java │ ├── com.xxx.dao # 数据访问层只和 SQL 打交道 │ │ ├── ArtworkDao.java │ │ └── ArtworkDaoImpl.java │ ├── com.xxx.entity # 实体类对应数据库表 │ │ └── Artwork.java │ ├── com.xxx.util # JDBC 工具类、字符串处理等 │ │ ├── DBUtil.java │ │ └── StringUtil.java │ └── com.xxx.filter # 过滤器编码处理、登录校验 │ └── EncodingFilter.java ├── web/ │ ├── admin/ │ │ ├── artwork_list.jsp │ │ └── artwork_add.jsp │ ├── index.jsp # 前台首页 │ ├── artwork_detail.jsp # 作品详情页 │ └── WEB-INF/ │ ├── web.xml # Servlet 映射、过滤器、欢迎页声明 │ └── lib/ # 项目依赖的 jar 包 └── sql/ └── artwork_exhibition.sql # 建库建表 初始数据脚本凡是能把这些目录完整交付出来的源码就算代码写得参差业务跑通基本没问题。反过来只丢几个散落的 Java 文件、没有 web.xml 和 sql 目录的“源代码”你要花大量时间去猜测表结构和 URL 映射那种项目我会直接放一边。为什么要按 controller / service / dao 分包核心目的是让每一层的职责能被单独验证。Servlet 只做三件事收参数、调 Service、把结果放到 request 域里转发给 Jsp。真正查库的代码只在 DAO 层出现这样以后从 MySQL 换成别的数据库、或者把查询逻辑改成分页都不需要动页面。这种分层在 Spring Boot 里演化成了 Controller、Service、Repository如果你照着这份源码把每一层都读懂后面学框架会轻松得多。2.2 一次作品列表请求的完整流转我一般会从列表页这个最普通的场景入手因为一个请求从浏览器到数据库再回到浏览器的路径直接展示出 Servlet、Jsp、MySQL 三者怎么配合。请求地址是/artwork/list对应的 Servlet 大致长这样WebServlet(/artwork/list) public class ArtworkListServlet extends HttpServlet { private ArtworkService artworkService new ArtworkServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 接收分类参数没传就查全部 String categoryId req.getParameter(categoryId); // 2. 业务逻辑交给 Service 层Servlet 不直接写 SQL ListArtwork list artworkService.listArtworks(categoryId); // 3. 把结果塞进 request 域转发给 Jsp 渲染 req.setAttribute(artworkList, list); req.getRequestDispatcher(/artwork_list.jsp).forward(req, resp); } }这段代码有几个点值得细看。第一WebServlet注解是 Servlet 3.0 之后的写法省去了在 web.xml 里一个个配servlet和servlet-mapping的重复劳动也正好对应“servlet生命周期”里最常见的考点容器启动时扫描注解完成注册init()只执行一次之后每次请求都走service()再分发到doGet或doPost项目关闭时执行destroy()。第二这里用的是forward而不是sendRedirect。转发是服务器内部的跳转Servlet 把artworkList放进了 request 域同一个请求到达 Jsp 时还能用request.getAttribute(artworkList)取到数据重定向会发起第二次 HTTP 请求request 里的属性就丢了届时就只能去 session 域取值。很多新手在这里改代码翻车改完发现列表页空白就是没想清楚转发和重定向的数据携带方式差异。对应的 Jsp 页面用 JSTL 标签渲染列表% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:forEach items${artworkList} vara div classcard img src${a.coverPath} alt${a.title} / h3${a.title}/h3 p作者${a.author}/p a hrefartwork/detail?id${a.id}查看详情/a /div /c:forEach注意${artworkList}能直接用是因为 Servlet 里setAttribute的 key 是artworkList。许多课程设计源码会在 Jsp 里直接写%request.getAttribute(artworkList)%再手动强转那种写法也能跑但页面里混进 Java 代码后维护成本会上升。如果你拿到手的源码到处是 scriptlet我建议把列表展示逻辑改成 JSTL 的forEach这是改造过程里性价比最高的一步。2.3 为什么 SQL 要放在 DAO 层而不是 Servlet 里有些非科班写的Servlet项目会直接把 JDBC 代码写进 Servlet 的doPost里建连、写 SQL、处理结果集、关闭资源全部堆在一个方法中。这种写法在小项目里能跑但你要新增一个“按作者查询作品”的接口就得把整段 JDBC 复制一遍要是某天数据库密码改了你需要在十几个 Servlet 里找DriverManager.getConnection。分层之后数据库的连接和释放被收敛到 DAO 层其他层永远只面向接口编程。public class ArtworkDaoImpl implements ArtworkDao { Override public ListArtwork findByCategoryId(String categoryId) { String sql SELECT id, title, author, cover_path, create_time FROM artwork WHERE category_id ? AND status 1 ORDER BY create_time DESC; ListArtwork list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, categoryId); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Artwork a new Artwork(); a.setId(rs.getInt(id)); a.setTitle(rs.getString(title)); a.setAuthor(rs.getString(author)); a.setCoverPath(rs.getString(cover_path)); a.setCreateTime(rs.getTimestamp(create_time)); list.add(a); } } } catch (SQLException e) { throw new RuntimeException(查询作品列表失败, e); } return list; } }这段代码演示了两件重要的事。第一用PreparedStatement而不是字符串拼接 SQLcategoryId以参数形式传入既避免 SQL 注入又不用手工处理单引号转义。第二Java 7 的 try-with-resources 语法保证Connection、PreparedStatement、ResultSet都会自动关闭。如果你在源码里看到的是裸conn.createStatement()加 finally 里一堆if (rs ! null) rs.close();那说明代码还停留在 Java 6 的写法功能没问题但连接泄漏风险高。这个项目里还有一个容易被忽略的组件是 Filter。编码过滤器要放在所有 Servlet 之前执行因为请求参数能不能被正确解码直接决定后续业务层拿到的是不是乱码。登录校验过滤器也要放在这一层如果session里没有管理员信息直接重定向到登录页而不必在每个 Servlet 里重复写判断。一个好用的展览系统源码里应该能同时找到这两个 Filter它们不承担业务逻辑但决定了整个系统的健壮性。3. 本地跑通最小闭环IDEATomcatMySQL 的配置顺序与关键参数3.1 版本组合怎么选才不会被老代码绊倒标题里没有写明具体版本这类毕设源码最常见的搭配是 JDK 1.8 Tomcat 8.5/9.0 MySQL 5.7也有不少新一点的工程直接在 MySQL 8.0 上写。我一般这么选JDK 用 1.8兼容性最好WebServlet注解、lambda 表达式都能用Tomcat 用 9.0它对应 Servlet 4.0跑 Servlet 3.1 的老代码完全没问题MySQL 用 5.7 比较省心如果电脑里已经装了 8.0 也没必要卸载只要在 JDBC URL 里多带两个参数详见 5.2。“vscode编写servlet代码需要怎么配置环境”这类问题常见但我的建议是这种带 webapp 目录结构的传统工程直接用 IDEA 打开最省事。VSCode 需要手动装 Java Extension Pack再配 Tomcat Server 插件构建路径上稍有不一致就会在启动时报 ClassNotFoundIDEA 的 Community 版就能建 Jsp 项目Ultimate 版对 Tomcat 集成更顺滑。“idea新建jsp项目”时记得选 Java Enterprise 的 Web Application 模板然后把源码里的 src 和 web 目录分别标记成 Sources Root 和 Web Resource Directory。3.2 先建库建用户连接参数才不会踩雷拿到 sql 脚本后我建议不要直接双击用文本编辑器打开再复制到命令行而是在命令行或 Navicat 里先建一个专用数据库和专用账号避免用 root 跑应用。命令是通用的CREATE DATABASE artwork_exhibition DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; -- 创建应用账号密码按项目要求改 CREATE USER art_adminlocalhost IDENTIFIED BY Art2024; -- 只给这一个库的权限不要给全局权限 GRANT ALL PRIVILEGES ON artwork_exhibition.* TO art_adminlocalhost; FLUSH PRIVILEGES;库名用了utf8mb4而不是老脚本里常见的utf8。原因很简单utf8在 MySQL 里最多存 3 个字节遇到生僻字或 Emoji 就可能落库失败utf8mb4是它的超集同样能存中文行为更可预期。 建完库之后找到源码里读数据库配置的db.properties或DBUtil.java把连接参数对齐。最常见的一行配置长这样jdbc.urljdbc:mysql://localhost:3306/artwork_exhibition?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameart_admin jdbc.passwordArt2024这些参数不是随便写的。useUnicodetruecharacterEncodingutf8保证中文以 UTF-8 编码传输不配的话 Service 层拿到请求参数就开始乱码serverTimezoneAsia/Shanghai解决 MySQL 服务器时区与本地不一致导致的 8 小时偏差常见报错是The server time zone value Öйú±ê׼ʱ¼äuseSSLfalse是为了避免 MySQL 8 默认开启 SSL 后本地环境握手失败这就是很多人遇到的“mysql ssl连接错误”allowPublicKeyRetrievaltrue配合 MySQL 8 的caching_sha2_password认证插件使用不加会在连接阶段直接抛Public Key Retrieval is not allowed。3.3 部署到 Tomcatwar 和 war exploded 有什么区别在 IDEA 里配置 Tomcat 部署时你会看到两种 Artifact 类型war和war exploded。开发调试阶段我强烈建议选war exploded它把项目按目录结构直接展开改一个 Jsp 文件刷新页面就能看到效果不需要重新打包war是把整个 webapp 压缩成一个包适合最后交付也就是“传统jsp项目打包war”这个操作对应的工作流。如果临时电脑上没有 IDEA命令行部署也很直接把打好的 war 包丢进 Tomcat 的 webapps 目录即可cp artwork_exhibition.war $CATALINA_HOME/webapps/ cd $CATALINA_HOME/bin ./startup.sh # 启动后访问 http://localhost:8080/artwork_exhibition/Tomcat 会自动解压 war 包。用这种方式部署要注意两点一是解压目录名就是 context path访问时要带项目名二是如果重新部署前没有先删掉旧目录可能出现新旧 class 混用的问题。 如果你在服务器上有 Nginx要记住“nginx支持jsp吗”的答案是否定的Nginx 只处理静态资源Jsp 请求必须反向代理给 Tomcat配置里写proxy_pass http://127.0.0.1:8080;即可。端口冲突是另一个高频问题。8080 被其他进程占用时Tomcat 启动日志会报Port already in use改conf/server.xml里的 ConnectorConnector port8081 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /改完端口后数据库、前端跨域等联调参数都要跟着变所以我不建议轻易换端口优先找到占用 8080 的进程处理掉。3.4 连接池参数不要只看见 maxActive很多源码里所谓“连接池”其实只是个DriverManager.getConnection的简单封装每次请求新建连接、用完关闭。这种写法在并发测试下一压就出Too many connections。常见做法是引入 DBCP、C3P0 或 Druid其中 Druid 因为自带监控页面在课程设计和中小型项目里见到得最多。一个能落地的连接池配置长这样# 初始化连接数项目启动时预先建好 5 个连接 initialSize5 # 最大活动连接数超过这个数新请求需要等待 maxActive20 # 最大空闲连接数低于这个数池子会补建连接 minIdle5 # 获取连接的超时时间单位毫秒60000 表示 60 秒拿不到连接就报错 maxWait60000 # 验证连接是否有效的 SQL validationQuerySELECT 1 # 借出连接超过 180 秒未归还强制回收 removeAbandonedTimeout180 removeAbandonedtrue参数表里最容易被忽略的是validationQuery。MySQL 连接空闲超过 8 小时会被服务端断开连接池里如果还保存着这些“死连接”下一次借出时执行 SQL 就会报Communications link failure。加了validationQuerySELECT 1之后连接借出前会被验证失效连接自动丢弃。removeAbandoned则是防呆设计如果代码里某个finally块漏写了conn.close()Druid 会在 180 秒后强制回收这个连接避免连接池耗尽。日志里看到removeAbandoned相关的 WARN先别急着屏蔽那大概率是代码里有连接泄漏多查PreparedStatement是否在 try-with-resources 里关闭了。4. 数据库设计与初始化从四张核心表到图片存储的边界4.1 核心表结构与建表 SQL源码里如果只有一个artwork_exhibition.sql文件那我认为这是一份合格交付如果连 SQL 脚本都没有只让你用 Navicat 手工建表那这个源码的完整度要打折扣。对于艺术作品展览系统最核心的四张表是管理员表、分类表、作品表、轮播图表。建表语句我按常见做法直接给出来-- 管理员表 CREATE TABLE sys_admin ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录账号, password VARCHAR(64) NOT NULL COMMENT 存 MD5 后的密文, real_name VARCHAR(50) DEFAULT COMMENT 姓名, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT管理员表; -- 作品分类表 CREATE TABLE art_category ( id INT PRIMARY KEY AUTO_INCREMENT, cate_name VARCHAR(50) NOT NULL COMMENT 分类名如油画/水墨/摄影, sort_order INT DEFAULT 0 COMMENT 排序权重数字越小越靠前 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT作品分类表; -- 作品表 CREATE TABLE artwork ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 作品标题, author VARCHAR(50) NOT NULL DEFAULT COMMENT 作者, category_id INT NOT NULL COMMENT 所属分类, cover_path VARCHAR(255) DEFAULT COMMENT 封面图路径存相对路径, detail_pics VARCHAR(1000) DEFAULT COMMENT 详情图路径多个用分号分隔, description TEXT COMMENT 作品描述, status TINYINT DEFAULT 1 COMMENT 1 上架0 下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 录入时间, CONSTRAINT fk_art_category FOREIGN KEY (category_id) REFERENCES art_category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT作品表; -- 轮播图表 CREATE TABLE sys_banner ( id INT PRIMARY KEY AUTO_INCREMENT, banner_name VARCHAR(50) DEFAULT , image_path VARCHAR(255) NOT NULL, link_url VARCHAR(255) DEFAULT COMMENT 点击后跳转的地址, sort_order INT DEFAULT 0 COMMENT 排序mysql设置默认值为0 的典型场景, status TINYINT DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT首页轮播图;建表时有几个细节值得按同样标准检查。第一凡是会作为查询条件的字段比如username、category_id、status都要在表结构里体现索引主键用INT AUTO_INCREMENT简单直接除非将来数据量过亿否则不需要 UUID 主键。第二状态字段用TINYINT而不是字符串上架/下架既省空间又能加索引页面显示时再通过标签或三元表达式转成文字。第三文本字段给TEXT路径字段给VARCHAR(255)不要把图片路径设计成TEXT因为索引长度有限制。4.2 时间字段的取舍DATETIME 还是 TIMESTAMP艺术作品表里的create_time用DATETIME还是TIMESTAMP不仔细想会埋一个隐性坑。TIMESTAMP的范围只到 2038 年而且它在驱动层会按照当前时区做转换DATETIME范围大得多也不会自动时区转换。对展览系统这种长期要展示“录入时间”的业务我一般选DATETIME配合连接 URL 里的serverTimezoneAsia/Shanghai时间逻辑最直观。真正容易翻车的是 Jsp 页面的时间格式化。如果实体类里createTime用的是java.util.Date页面可以用标签库格式化% taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % fmt:formatDate value${artwork.createTime} patternyyyy-MM-dd HH:mm /但如果源码里实体类写的是java.time.LocalDateTime上面这段会直接报错因为 JSTL 的formatDate只认java.util.Date。两种解决办法一种是在 DAO 层取数时就转成java.util.Date保持和页面标签兼容另一种是前台展示时直接调用artwork.createTime.toString()再截取前 16 个字符虽然不够优雅但能最快解决问题。 这种问题在“jsp个人信息展示页面”里也会遇到凡是页面上要显示一条记录创建时间的都建议先在 SQL 里验证字段类型再决定页面怎么写。4.3 初始化数据的写法编码、默认值和密码初始化脚本除了建表还要把几条必须的数据写进去否则项目启动后一片空白登录都进不去。写法上注意三点。第一脚本开头用SET NAMES utf8mb4;保证命令行导入时中文不乱码第二插入语句显式列名不要写成INSERT INTO sys_admin VALUES (...)因为表结构一旦调整脚本就作废了第三管理员密码存 MD5 而不是明文初始账号密码要写清楚。一条可复用的基础数据插入长这样SET NAMES utf8mb4; -- 初始管理员账号 admin密码 admin123 -- 注意入库的是 MD5(admin123) 的密文不是明文 INSERT INTO sys_admin(username, password, real_name) VALUES (admin, MD5(admin123), 系统管理员); INSERT INTO art_category(cate_name, sort_order) VALUES (油画, 1), (水墨, 2), (摄影, 3); INSERT INTO sys_banner(banner_name, image_path, link_url, sort_order) VALUES (首页主推, upload/banner1.jpg, artwork/detail?id1, 1);密码这个问题我要多说一句很多老脚本直接把123456明文写在表里演示是方便了但只要拿到这个源码的人看一眼数据库所有管理员账号就全暴露了。维护这类系统时最直接的“后悔药”是把初始化数据里的密码改成MD5(你的新密码)登录页核对时把用户输入也做一次 MD5 再比对。MySQL 里MD5()函数在脚本里可以直接用但如果源码里已经有登录逻辑在对password做二次加密那就要先读代码再决定初始化数据怎么写别让加密变成双重叠加。另外用 MySQL Workbench 或 Navicat 导入脚本时要选择“以 UTF-8 编码运行 SQL”而不是直接复制粘贴。Windows 命令行默认编码往往是 GBK中文注释会在导入时报语法错误或变成乱码。4.4 图片只存路径不存文件上传目录怎么设计我刚拿到这类源码时最怕看到数据库里存的是D:/upload/xxx.jpg这种绝对磁盘路径。项目换一台机器部署路径就全废了。正确做法是数据库只存相对路径上传的物理文件放在 webapp 的 upload 目录下前端页面用项目上下文去拼完整地址。上传 Servlet 的核心写法如下// 1. 得到 upload 目录的真实磁盘路径 String realBase getServletContext().getRealPath(/upload); File uploadDir new File(realBase); if (!uploadDir.exists()) { uploadDir.mkdirs(); } // 2. 用 UUID 重命名文件避免中文文件名和特殊字符问题 String ext fileName.substring(fileName.lastIndexOf(.)); String newName UUID.randomUUID().toString().replace(-, ) ext; File dest newFile(uploadDir, newName); // 3. 把相对路径写进数据库upload/新文件名.jpg String dbPath upload/ newName;数据库存upload/xxx.jpg之后页面展示时用src${pageContext.request.contextPath}/upload/xxx.jpg这样部署在任意一台机器上都不会出现死路径。这里要注意两个边界第一getRealPath在开发环境指向 IDEA 的 target 目录在发布环境指向 Tomcat 解压目录war 包一旦重新部署之前手工传进 upload 目录的文件会被清掉所以交付时要么把图片做成静态资源单独放要么把 upload 指到CATALINA_HOME/conf里的虚拟目录第二不要把图片存进 MySQL 的BLOB字段列表页一次查出几十张图会让查询和网络带宽同时变慢对这个体量的项目来说文件系统加相对路径是最平衡的方案。5. 避坑清单中文乱码、SSL连接、旧 class 缓存、Session 失效、图片路径5.1 中文乱码三个层面必须同时统一现象数据库里的中文正常但 Servlet 往页面输出中文变成???或者表单提交后入库的中文变成乱码严重的时候整个页面出现一堆问号刷新也没用。原因乱码很少只有一个来源。Jsp 页面本身的编码、Servlet 读取请求参数时的编码、连接 MySQL 时 URL 里的编码、数据库表默认字符集这四个环任何一环断掉都会乱码。最常见的组合是 Jsp 写了UTF-8但 Servlet 里没有request.setCharacterEncoding(UTF-8)于是 POST 提交的中文被当成 ISO-8859-1 解析到 Service 层就已经是错的。解决用一个编码过滤器统一处理在 web.xml 里把它配置到所有请求之前filter filter-nameencodingFilter/filter-name filter-classcom.xxx.filter.EncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingFilter 内部就三行request.setCharacterEncoding(encoding)、response.setCharacterEncoding(encoding)、response.setContentType(text/html;charset encoding)。同时把 Jsp 页头写成pageEncodingUTF-8数据库表统一用utf8mb4JDBC URL 带上characterEncodingutf8。排查乱码时按这个顺序检查哪个环节缺了补哪个。记住一个原则三个层面全部统一成 UTF-8不要留一个“例外”。5.2 MySQL 8 驱动报 Public Key Retrieval is not allowed现象代码在 MySQL 5.7 上跑得好好的换到 MySQL 8.0 之后启动项目或第一次执行查询就抛Public Key Retrieval is not allowed连接直接失败。原因MySQL 8.0 默认认证插件是caching_sha2_password客户端第一次连接时需要从服务器端获取 RSA 公钥来加密密码但 JDBC 驱动出于安全考虑默认不允许自动获取公钥于是连接阶段就失败。解决两种方式任选。第一种在 JDBC URL 末尾追加参数jdbc:mysql://localhost:3306/artwork_exhibition?useSSLfalseallowPublicKeyRetrievaltrue第二种建用户时指定mysql_native_password插件CREATE USER art_adminlocalhost IDENTIFIED WITH mysql_native_password BY Art2024;个人建议用第二种因为allowPublicKeyRetrievaltrue在公网环境下会带来中间人攻击的隐患本地开发无所谓但如果你要把项目部署到有公网地址的服务器上更稳妥的是指定旧版插件。还要顺带检查 mysql-connector-java 的版本MySQL 8 对应的驱动版本要在 8.x 以上老驱动对新的认证协议支持不完整报错现象是Unable to load authentication plugin caching_sha2_password。5.3 改了 Java 代码页面却还是旧行为现象修改了 Servlet 里的逻辑重新编译IDEA 也显示“Build success”但刷新页面看到的效果和改动前一模一样甚至删除某个方法后页面依然正常调用仿佛改动没生效。原因这个话题属于典型的“改代码不生效”翻车现场。大多数情况是 IDEA 的 Artifact 配置成了war而不是war exploded每次构建只是把旧的 class 重新打包页面对应的目录没有同步另一部分情况是 Tomcat 的work/Catalina/localhost目录里缓存了旧 class 和解压后的资源Redeploy 只是把新 class 覆盖上去缓存的旧版本还在。解决按顺序排查。先到项目的target/classes目录确认新的.class文件时间戳是否更新了再打开 Tomcat 的work/Catalina/localhost下对应项目目录手动删掉临时文件最后在 IDEA 的 Run Configuration 里把 On Update Action 改成Redeploy而不是Restart Server。真正稳妥的做法是Stop 服务Remove Artifact重新 Deploy再 Start。这套流程看起来笨但能把这类玄学问题一次清干净。 不要迷信 IDE 右下角的“Build completed successfully”那个绿条只代表编译通过不代表部署目录已经同步。5.4 登录状态失效跳转回首页又要重新登录现象管理员登录成功后页面跳转到后台首页但点任意一个功能又跳回登录页刷新一下session 里的管理员信息也消失了。原因和“jsp个人信息展示页面”里常见的用户登录问题同源。首先是跳转方式问题登录成功后如果用sendRedirect跳转到后台页面浏览器会发起一次新的 HTTP 请求而之前 session 里存的数据还在正常来说没有问题真正的问题往往出在登录状态没有写入 session或者登录过滤器的拦截路径配置错误把所有请求都拦下来要求登录。另一个经常被忽视的原因是同一浏览器同时访问了两个不同项目名的 webappCookie 里保存了多个 JSESSIONID路径匹配混乱导致 session 串了或者被覆盖。解决登录成功后的写法应该是在 session 里放对象后再用重定向去后台首页注意不要使用forward因为重定向会重新发起请求用户能刷新而不重复提交表单if (admin.equals(username) MD5Util.md5(password).equals(user.getPassword())) { // 登录成功把用户对象放入 session 域 req.getSession().setAttribute(admin, user); resp.sendRedirect(admin/index.jsp); } else { // 失败时转发回登录页并携带错误信息 req.setAttribute(errorMsg, 账号或密码错误); req.getRequestDispatcher(admin/login.jsp).forward(req, resp); }登录过滤器拦截时要放行登录页和静态资源只拦截/admin/*并在 session 里取不到admin时重定向到login.jsp。另外检查web.xml里的 session 超时时间单位是分钟session-config session-timeout30/session-timeout /session-config如果以上都正常最后去浏览器开发者工具里看 Cookie 的 Path 是不是/如果 Path 指向了一个具体的项目子路径换一个 URL 访问时 Cookie 就带不过去。5.5 列表页图片能显示详情页图片红叉现象作品列表页的缩略图正常显示点进详情页后图片区域是红叉或者空白。前台换一台电脑访问列表页的图也不出来了。原因最典型的两个。一是数据库里存的是绝对磁盘路径比如D:\workspace\upload\1.jpg列表页碰巧能显示是因为图片请求在同一次会话里复用了某种缓存换一台机器后这个路径在服务器上根本不存在二是详情页拼接路径时用了错误的相对路径artwork_detail.jsp位于/artwork/detail这种二级路径下upload/xxx.jpg会被浏览器解析成/artwork/upload/xxx.jpg自然 404。解决统一在页面里用${pageContext.request.contextPath}拼接图片路径img src${pageContext.request.contextPath}/${artwork.coverPath} alt${artwork.title} /同时保证数据库里存的只有upload/xxx.jpg这一段相对路径不出现盘符、不以/开头。如果是生产环境需要把图片放到 Tomcat 之外可以在server.xml的 Host 节点下配置虚拟目录Context docBase/data/artwork_upload path/upload reloadablefalse /这样数据库里的upload/xxx.jpg会被映射到/data/artwork_upload/xxx.jpgwar 重新部署也不会丢文件。最后检查一下upload目录的读写权限Linux 服务器上如果 Tomcat 进程对 upload 目录没有写权限上传时会报FileNotFoundException这个报错信息不会提示“没有权限”排查起来最容易绕弯路。6. 进阶按业务主流程做一轮验收再决定这个项目值不值得往下加拿到源码并部署成功之后先不要急着改业务而是按主流程把系统从登录到展示完整走一遍。下面是我常用的验收清单适合任何基于 ServletJspMySQL 的展览系统验证项操作步骤预期结果管理员登录用初始化脚本里的 admin 账号登录跳转后台不再弹回登录页分类维护后台新增“书法”分类前台首页分类下拉框能选到它作品上传提交标题、作者、分类、封面图列表页出现新作品且图片可打开前台浏览匿名访问首页作品正常展示分类切换和详情页无 500下架作品后台把作品状态改为 0前台不再展示数据库记录仍保留跑清单时顺手执行几个 SQL能快速判断数据层是否健康-- 前台可见作品数量 SELECT COUNT(*) FROM artwork WHERE status 1; -- 关联查询作品标题和它所属的分类名 SELECT a.id, a.title, c.cate_name FROM artwork a LEFT JOIN art_category c ON a.category_id c.id; -- 查看当前数据库连接情况排查连接池是否被耗尽 SHOW FULL PROCESSLIST;如果这个清单全部通过再想加功能我建议先加“作品推荐”而不是直接上复杂的统计报表。给artwork表加一个is_recommend TINYINT DEFAULT 0字段后台多一个勾选框前台首页按is_recommend 1查询即可。这个改动会同时涉及数据库、DAO、Servlet、Jsp 四个层面正好把前面理解的分层再完整练一遍。 至于要不要从这套老栈迁移到 Spring Boot我的判断是只要项目还在稳定运行就没有必要为了“新”而重写但如果你打算以这个项目为基础继续找 JavaWeb 开发工作那可以对照这份源码把 Controller、Service、Mapper 三层的对应关系在 Spring Boot 里重新实现一遍两相对照收获会很大。我现在拿到这类老项目已经养成了一个习惯先按清单跑主流程再动代码。以前图快直接改 Jsp 界面最后发现一个查询参数问题要翻三层代码才查明白反而更慢。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →