news 2026/9/11 15:43:40

SSM图书角系统从环境配置到部署排错全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM图书角系统从环境配置到部署排错全指南

简介:面向计算机专业学生的校园图书角管理系统毕业设计项目,基于SSM(Spring+SpringMVC+MyBatis)+JSP+HTML开发,前后端代码完整,代码注释详细,新手也能快速看懂。包内共1293个文件,其中包含128个Java类、113个JSP页面、364个JavaScript脚本、146个CSS样式表,以及PNG/JPG图片素材、MySQL数据库脚本和部署说明,压缩包仅49.38MB。内容预览中可见ElementUI、Bootstrap等前端组件及多种备份文件,便于对照学习与排错,系统涵盖图书信息管理、借阅管理、分类管理等常用模块,功能完善、界面美观,部署时建议配合IDEA、Navicat等工具。已有59人浏览学习,适合作为毕业设计、课程设计或SSM框架练手项目。按部署说明导入项目并配置数据库后即可运行体验,项目采用分层架构,代码结构清晰,经严格调试可运行,能帮助读者完整掌握从环境搭建到系统上线的全过程,具备较高的实用和参考价值。

1. 别等答辩前一晚才看这份 SSM 图书角系统拆解

每年毕业季都有人问同一个问题:基于 SSM 的图书角管理系统到底怎么跑起来?我见过太多同学把压缩包解压后,连数据库都导不进去,卡在 Tomcat 端口、MyBatis 映射路径这类最基础的地方,最后只能抱着笔记本去机房求老师帮忙。这类 SSM+JSP+HTML 的毕业设计项目,本质上是一条从页面到数据库的完整请求链:JSP 负责展示和交互,SpringMVC 负责路由和传参,Service 层处理业务,MyBatis 和 MySQL 负责持久化。项目结构并不复杂,真正让人头疼的是环境匹配、配置顺序和隐式约定。这篇博文就顺着这个管理系统的骨架,把环境搭建、数据库设计、核心配置到部署排错整条链路讲透。适合手里有源码但跑不起来的人,也适合想自己动手把系统重写一遍的初学者。

2. 跑通 SSM 项目前先校准环境:JDK、Maven、Tomcat 与 MySQL 的版本匹配

2.1 SSM 框架耦合度低,但运行环境对版本极其敏感

SSM 是 Spring + SpringMVC + MyBatis 的组合。Spring 负责对象管理和事务,SpringMVC 负责 HTTP 层的请求分发,MyBatis 负责 SQL 与 Java 对象的映射。这套组合的好处是分层清晰、职责单一,坏处是三个框架各自迭代,版本交叉后容易出现莫名其妙的兼容问题。很多毕业设计源码传了不知道多少手,pom.xml 里的依赖版本可能还是 2018 年前后的写法,而你本地装的是 JDK 17,Tomcat 10,那基本是跑不动的。

从我接触过的项目经验来看,这类校园管理系统最稳妥的组合是 JDK 1.8 + Maven 3.6.3 + Tomcat 8.5 + MySQL 5.7。这个组合经过了大量毕业设计项目的验证,网上能找到的解决方案也最多。如果你机器上已经装了更高版本的 JDK,可以不用卸载,在 IDEA 里单独配置 Project SDK 为 1.8 即可,具体路径是 File → Project Structure → Project → SDK,下拉选择已安装的 1.8。如果没有,需要去 Oracle 官网下载 JDK 8u 版本安装,或者用较为轻量的 OpenJDK 8。注意环境变量 JAVA_HOME 要指向这个 JDK 8 的安装目录,不然 Maven 和 Tomcat 可能拿不到正确的 Java 环境。

2.2 导入源码前的三个关键检查点

拿到压缩包后不要急着打开 IDEA,先做三件事:检查 pom.xml 里的依赖版本、检查 web.xml 的 Servlet 版本声明、检查数据库脚本的编码格式。Web.xml 中web-app标签的version属性如果是 3.1 或 4.0,就要确保 Tomcat 8.5 以上支持;如果是 2.5,那 Tomcat 7 都行,但需要 Java 7 以上。这里有一个常见的坑:如果 web.xml 里声明的是 Servlet 4.0,而你用的是 Tomcat 8.0,启动时会直接报UnsupportedClassVersionError或者LifecycleException

数据库脚本也要提前看。用记事本或 VSCode 打开 SQL 文件,检查第一行是否有SET NAMES utf8mb4或类似字符集设置。很多项目表结构里包含中文备注,如果你的 MySQL 客户端默认字符集不是 utf8mb4,导入时会出现一批警告,虽然大部分不影响表结构,但数据表里如果有中文种子数据,就会直接变成乱码。最稳妥的做法是先用 Notepad++ 或 VSCode 把 SQL 文件转为 UTF-8 编码保存,再用命令行或 Navicat 导入。

版本确认后就可以用 IDEA 导入了。选择 File → New → Project from Existing Sources,定位到解压后的目录,选择 Maven 类型,IDEA 会自动识别 pom.xml。首次加载时会下载大量依赖,国内网络建议在 Maven 的 settings.xml 里配置阿里的镜像仓库,配置方式是在mirrors节点下加这段:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

这个镜像源配置在 settings.xml 的<mirrors>标签内,配置后 Maven 对中央仓库的下载请求都会指向阿里云。如果项目已经下载到一半出现红叉,可以在 IDEA 右侧 Maven 面板里点击刷新按钮,重新解析依赖。

2.3 JDK、Maven、Tomcat、MySQL 版本匹配参考表

下面这张表是我在着手环境配置时常用的参考,你也可以根据实际手头已有的软件灵活调整,但要保证大版本不跳档:

组件推荐版本备选版本关键注意事项
JDK1.8(8u191+)1.7(仅配合 Tomcat 7)高于 11 时部分老框架反射有问题
Maven3.6.33.3.93.6+ 对 JDK 8 支持良好
Tomcat8.5.x9.0.x9.0 需要 JDK 8 以上,兼容 Servlet 4.0
MySQL5.7.x8.0.x8.0 需要驱动com.mysql.cj.jdbc.Driver
MySQL 驱动5.1.478.0.11+8.0 驱动必须加时区参数

这个版本组合是踩过很多坑之后沉淀下来的经验值,尤其是 MySQL 驱动这一点。如果你本地是 MySQL 8.0,而 pom 里是 5.1.47 的驱动,连接时八成会出现Communications link failure或者Public Key Retrieval is not allowed,后者的解决办法是连接 URL 加allowPublicKeyRetrieval=true&useSSL=false,但更省事的办法是直接把驱动版本升到 8.0.11 以上。驱动版本由 pom.xml 中的<mysql-connector-java>依赖版本号控制,改完后重新加载 Maven 即可。

3. 手写 SSM 核心配置:从 web.xml 到 mybatis-config 的完整装配顺序

3.1 web.xml 里的监听器、DispatcherServlet 与编码过滤器谁先谁后

SSM 项目没有 Spring Boot 那样的自动配置,一切靠 web.xml 手动装配。web.xml 是 Web 应用的入口描述文件,它决定了 Spring 容器何时初始化、SpringMVC 的 DispatcherServlet 拦截哪些请求、字符编码过滤器是否全局生效。我见过很多系统启动后乱码或者 404,问题往往就出在这里。

一个标准的 SSM web.xml 需要依次完成以下几件事:通过 ContextLoaderListener 初始化 Spring 根容器,加载 classpath 下的 applicationContext.xml;配置 DispatcherServlet 并加载 spring-mvc.xml;配置 CharacterEncodingFilter 并设置 encoding 为 UTF-8、forceEncoding 为 true;配置欢迎页和 session 过期时间。以下是常见的配置骨架:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>campus-book-corner</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

这段配置文件的关键点有两个:DispatcherServlet 拦截/而不是*.do,意味着所有请求都进 SpringMVC,静态资源要额外放行;ContextLoaderListener 先初始化根容器,DispatcherServlet 再初始化自己的子容器,两个容器各管一部分 Bean。如果你发现 Controller 里的@Autowired注入 Service 失败,先检查 Service 是否被根容器扫描、Controller 是否被 spring-mvc.xml 扫描,两个扫描范围重叠就会导致 Bean 重复定义,不重叠就会导致找不到依赖。

3.2 spring-mvc.xml 的组件扫描、注解驱动与静态资源放行

spring-mvc.xml 是 SpringMVC 的核心,它决定去哪里找 Controller、是否启用注解解析、视图解析器怎么拼 JSP 路径、静态资源怎么处理。常见的配置模板如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 当前容器的组件扫描范围,只扫 controller 包 --> <context:component-scan base-package="com.campus.book.controller"/> <mvc:annotation-driven/> <mvc:resources mapping="/static/**" location="/static/"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans>

这里最容易出问题的是<context:component-scan>的包路径。如果 Controller 在com.campus.book.controller,而你在注解类上写的是com.campus.book,会把 Service 和 Mapper 也扫进来,再由 MyBatis 的 MapperScannerConfigurer 扫一遍,就会出现 Bean 冲突。<mvc:annotation-driven/>是必须的,它负责注册默认的 HandlerMapping 和 HandlerAdapter,没有这一行你写的@RequestMapping全部不生效。

视图解析器的 prefix 和 suffix 决定了 Controller 返回的字符串如何定位到 JSP。如果项目 JSP 放在 WebContent 或 webapp 根目录,就写成/前缀;如果像这个模板里放在 WEB-INF 下,浏览器不能直接访问,但 Controller 可以转发到该路径,这种方式更安全,也是推荐做法。

3.3 MyBatis 的 Mapper 扫描、别名与 JDBC 连接参数

MyBatis 侧的配置在 applicationContext.xml 或单独的 mybatis-spring.xml 里,核心是 SqlSessionFactoryBean、MapperScannerConfigurer 和数据源三者之间的协作。很多系统启动报Invalid bound statement (not found),就是 SqlSessionFactoryBean 加载的 mapper XML 路径和 Mapper 接口包路径对不上。

建议的方向是把数据源独立成 jdbc.properties,复用性更好。项目中的 jdbc.properties 内容一般长这样:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/bookcorner?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=123456

如果用 MySQL 8.0,driver 要改成com.mysql.cj.jdbc.Driver,url 末尾还要加serverTimezone=Asia/Shanghai,否则会报时区错误。接着在 applicationContext.xml 里注入数据源:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:com/campus/book/mapper/*.xml"/> <property name="typeAliasesPackage" value="com.campus.book.entity"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.campus.book.dao"/> </bean>

MapperScannerConfigurer 的 basePackage 是 Mapper 接口所在的包,它通过扫描接口生成动态代理,然后注入到 Service 里。mapperLocations 指向 XML 文件,注意 XML 的命名空间必须和接口全限定名一致,<select>标签的 id 必须和接口方法名一致,否则会启动失败。typeAliasesPackage 配置实体类所在的包,这样就可以在 XML 里直接写resultType="Book"而不用写全路径。

3.4 数据库表设计:图书、用户、借阅记录三张表的字段与关联

图书角管理系统数据库的表结构通常围绕三个核心实体:图书、用户、借阅记录。图书表存书目信息,用户表存学生和管理员,借阅记录表存谁在什么时候借了什么书、什么时候还。一张完整的建表 SQL 大体是这样的:

CREATE DATABASE IF NOT EXISTS bookcorner DEFAULT CHARSET utf8mb4; USE bookcorner; CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL COMMENT '书名', author VARCHAR(50) DEFAULT NULL COMMENT '作者', publisher VARCHAR(80) DEFAULT NULL COMMENT '出版社', isbn VARCHAR(20) DEFAULT NULL COMMENT 'ISBN号', category VARCHAR(30) DEFAULT NULL COMMENT '分类', total INT DEFAULT 1 COMMENT '馆藏数量', remaining INT DEFAULT 1 COMMENT '可借数量', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT '图书表'; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(64) NOT NULL COMMENT '密码', role TINYINT DEFAULT 0 COMMENT '0学生 1管理员', student_no VARCHAR(20) DEFAULT NULL COMMENT '学号', phone VARCHAR(20) DEFAULT NULL COMMENT '联系电话' ) ENGINE=InnoDB COMMENT '用户表'; CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, book_id INT NOT NULL COMMENT '图书ID', user_id INT NOT NULL COMMENT '用户ID', borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '借出时间', due_time DATETIME DEFAULT NULL COMMENT '应还时间', return_time DATETIME DEFAULT NULL COMMENT '实际归还时间', status TINYINT DEFAULT 0 COMMENT '0借出 1已还 2逾期', KEY idx_book_id (book_id), KEY idx_user_id (user_id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES book(id), CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINE=InnoDB COMMENT '借阅记录表';

这里值得留意的字段是remaining。很多系统更新库存时只做减法,不检查是否为负,用户连续点两次借阅就会把库存借成负数,影响数据完整性。常见做法是在 Service 层先查 remaining 是否大于 0,再用UPDATE book SET remaining = remaining - 1 WHERE id = ? AND remaining > 0这样的语句保证原子性。外键在毕业设计里建议保留,它能体现你对关系型数据库的建模理解,但在高并发场景下外键会影响性能,你可以在文末的答辩准备里说明你清楚这个取舍。

4. 从 JSP 页面到数据库的完整请求链:图书借阅功能的代码是怎么串联的

4.1 JSP 里的表单如何提交到 Controller

JSP 在这个系统里负责两层事:渲染数据和接收用户操作。图书借阅页面上有一个表格,列出所有可借图书,每行右侧一个“借阅”按钮。这个按钮可以是一个包含 bookId 的表单,也可以是一个携带参数的链接。推荐后者的写法,因为借阅操作理论上应该是 POST 请求,但如果项目里没有做 CSRF 防护,GET 也能跑通。下面是表格中借阅按钮的 JSP 片段:

<form action="${pageContext.request.contextPath}/borrow/add" method="post" style="display:inline;"> <input type="hidden" name="bookId" value="${book.id}" /> <input type="hidden" name="userId" value="${sessionScope.loginUser.id}" /> <button type="submit">借阅</button> </form>

这段代码中${pageContext.request.contextPath}是 JSP EL 表达式,作用是动态获取项目根路径,避免因为部署时改了应用名导致路径 404。${book.id}是后端返回的图书对象属性。表单提交到/borrow/add这个 URL,由 SpringMVC 的注解映射处理。不需要在 JSP 里写任何 Java 代码,EL 表达式和 JSTL 标签已经够用。

4.2 Controller、Service、Mapper 三层的编码顺序

Controller 层只做参数接收和视图跳转,业务逻辑下沉到 Service 层,SQL 留在 Mapper XML。下面以借阅功能为例,展示从 Controller 到 Mapper 完整的调用链写法。Controller 需要接收前端传来的 bookId 和 userId,调用 Service 完成借阅逻辑,然后重定向回列表页:

@Controller @RequestMapping("/borrow") public class BorrowController { @Autowired private BorrowService borrowService; @PostMapping("/add") public String addBorrow(Integer bookId, Integer userId, Model model) { try { borrowService.borrowBook(bookId, userId); return "redirect:/book/list"; } catch (RuntimeException e) { model.addAttribute("errorMsg", e.getMessage()); return "forward:/book/list"; } } }

@PostMapping是 Spring 4.3 之后引入的组合注解,等价于@RequestMapping(value="/add", method=RequestMethod.POST)。方法参数中的Integer bookId由 SpringMVC 自动完成类型转换,从前端表单取同名参数。重定向到/book/list可以避免表单重复提交,这是 PRG 模式(Post/Redirect/Get)的常规操作。

Service 里要处理库存扣减和借阅记录插入的原子性,这不是一个简单的状态转移,而是一组必须同时成功的写入操作。用 Spring 的声明式事务注解实现最简单:

@Service public class BorrowServiceImpl implements BorrowService { @Autowired private BookMapper bookMapper; @Autowired private BorrowRecordMapper borrowRecordMapper; @Override @Transactional(rollbackFor = Exception.class) public void borrowBook(Integer bookId, Integer userId) { Book book = bookMapper.selectById(bookId); if (book == null) { throw new RuntimeException("图书不存在"); } if (book.getRemaining() <= 0) { throw new RuntimeException("图书已被借完"); } int rows = bookMapper.decreaseRemaining(bookId); if (rows == 0) { throw new RuntimeException("库存扣减失败,请重试"); } BorrowRecord record = new BorrowRecord(); record.setBookId(bookId); record.setUserId(userId); record.setDueTime(new Date(System.currentTimeMillis() + 30L * 24 * 3600 * 1000)); borrowRecordMapper.insert(record); } }

@Transactional注解由 Spring 的 AOP 实现事务增强,默认只在遇到 RuntimeException 时回滚。rollbackFor = Exception.class表示任何异常都回滚。这里先查再扣再插入,三步操作在同一个事务里,任何一个失败都会整体回滚,不会出现库存减了但借阅记录没生成的半截状态。

Mapper 接口定义与 XML 映射是关键一环,接口方法名与 XML id 必须一一对应:

public interface BookMapper { Book selectById(Integer id); int decreaseRemaining(Integer id); }

对应的 BookMapper.xml 中,decreaseRemaining使用了乐观锁式的条件更新:

<update id="decreaseRemaining"> UPDATE book SET remaining = remaining - 1 WHERE id = #{id} AND remaining > 0 </update>

这里的#{id}是 MyBatis 的预编译参数占位符,可以防 SQL 注入。AND remaining > 0是防止超借的关键条件,如果返回行数为 0,说明当前库存不足或图书不存在,Service 层可以抛出异常并回滚事务。

4.3 列表页查询的分页与搜索条件拼接

图书列表页在毕业设计里通常要做到分页和模糊查询,这也是答辩时老师会重点问的部分。用 PageHelper 插件最省事,但如果项目里没集成插件,手写 LIMIT 分页也就十来行代码。Controller 接收pageNumpageSize和关键字keyword,传给 Service,Service 构造查询条件传给 Mapper:

@GetMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String keyword, Model model) { int offset = (pageNum - 1) * pageSize; List<Book> bookList = bookMapper.selectPage(keyword, offset, pageSize); int total = bookMapper.countByKeyword(keyword); int totalPages = (int) Math.ceil(total * 1.0 / pageSize); model.addAttribute("bookList", bookList); model.addAttribute("totalPages", totalPages); model.addAttribute("currentPage", pageNum); model.addAttribute("keyword", keyword); return "book/list"; }

分页参数在前端由 JSP 拿到后渲染翻页按钮,点击时拼接新的 query string 发起新的 GET 请求。这里有一个隐蔽的问题是 SQL 里的模糊查询写法,MySQL 中用LIKE CONCAT('%', #{keyword}, '%'),而不要直接写'%${keyword}%',前者是预编译传参,后者是做字符串拼接,存在注入风险。

列表页还有一种常见需求是导出 Excel。检索结果里有“jsp实现数据导出为excel”这个方向,学生管理模块的老师通常会要求导出学生借阅明细。有两条路可以走:一是后端用 Apache POI 生成 xlsx 响应给前端;二是在 JSP 上直接响应Content-Type: application/vnd.ms-excel,把表格内容以 CSV 或 HTML Table 形式输出。毕业设计够用的话,用 POI 导出更显技术含量,代码量也不大:

@GetMapping("/export") public void export(HttpServletResponse response) throws IOException { List<BorrowRecordVO> records = borrowRecordMapper.selectAllWithBookAndUser(); response.setContentType("application/vnd.ms-excel;charset=utf-8"); response.setHeader("Content-Disposition", "attachment;filename=borrow_records.xls"); try (Workbook workbook = new HSSFWorkbook()) { Sheet sheet = workbook.createSheet("借阅记录"); // 创建表头行,写入字段名 // 遍历 records,逐行写入单元格 workbook.write(response.getOutputStream()); } }

导出接口返回的是一份文件流,不是 JSP 页面,所以 Controller 方法上不需要@ResponseBody,直接写 response 输出即可。

4.4 JSP 页面引用静态资源的相对路径问题

图书角管理系统里登录页和管理端页面都会引用 CSS 和 JS,如果你在 spring-mvc.xml 里配置了静态资源映射为/static/**,在 JSP 页面里正确的引用方式是:

<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/style.css" /> <script src="${pageContext.request.contextPath}/static/js/common.js"></script>

不用${pageContext.request.contextPath}直接写static/css/style.css的话,浏览器会基于当前 URL 拼路径。假如当前页面地址是/book/list,浏览器会请求/book/static/css/style.css,由于 DispatcherServlet 拦截了/,这个请求会进入 SpringMVC,匹配不到映射就直接 404。如果你在学生信息维护页面遇到过样式丢失、图片裂开,优先怀疑的就是这个相对路径问题。

5. 部署环节的实测排错:乱码、404 和数据库连不上的三个高频问题

5.1 Tomcat 启动成功但页面 404?先查 Artifacts 和访问路径

IDEA 里运行 SSM 项目,大多数人卡在第一步:Tomcat 能启动,但浏览器访问http://localhost:8080跳出 Tomcat 默认首页,访问/项目名就是 404。这种情况 90% 是 Artifacts 配置的问题。打开 File → Project Structure → Artifacts,确认输出类型是Web Application: Exploded,并且在 Deployment 标签页里已经把构建产物加入。更具体的说,IDEA 里要把 Tomcat Server → Deployment 中的 Application context 和项目访问路径对应起来。

应用上下文名称默认是项目名_war_exploded,一长串很难记,建议改成/bookcorner。然后浏览器访问http://localhost:8080/bookcorner就能打开首页。改完之后注意 URL 的大小写,Linux 服务器上区分大小写,Windows 上不区分,如果部署到云服务器上,路径写错就会白屏。

还有一种 404 只针对部分页面,比如/book/list能打开,/borrow/export报 404。看 IDEA 控制台的日志,如果出现No mapping found for HTTP request with URI [/borrow/export] in DispatcherServlet,说明 Controller 里没有对应映射,或者类被扫描到了但方法上的注解写错了。检查@GetMapping("/export")是否拼错,类上@RequestMapping("/borrow")是否缺失。

5.2 页面中文全是问号?字符集链条要逐段排查

乱码问题在 JSP+SSM 项目里几乎是必考题。乱码分两种情况:一种是从数据库读出来的中文变成问号,另一种是页面提交的中文存进数据库变成问号。前者的根源在数据库连接 URL 上,useUnicode=true&characterEncoding=utf8两个参数必须同时存在;后者的根源在 JSP 页面编码和 Tomcat 对 POST 请求的解码方式。

JSP 文件头部需要写上两行:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <meta charset="UTF-8">

同时 web.xml 里的CharacterEncodingFilter要放在所有 Filter 的最前面,并且forceEncoding设为 true,这样请求和响应都会被强制以 UTF-8 处理。如果这些都设置了还乱码,检查物理机上的 Tomcat 配置:conf/server.xml中 Connector 标签有没有加URIEncoding="UTF-8",8.5 版本默认是 UTF-8,但有的教学环境装的是老版本,需要手动加上。

数据库侧的检查位置是 URL 的characterEncoding=utf8参数,以及 MySQL 建表时的DEFAULT CHARSET=utf8mb4,两者字符集要一致。此外 MySQL 客户端在导入 SQL 文件时,如果用了 Source 命令,还要先执行SET NAMES utf8mb4;确保会话级字符集正确。

5.3 数据库连接失败:驱动、时区、密码三种情况

连接失败一般会在启动 Tomcat 时通过异常堆栈暴露出来,常见的报错是Cannot create PoolableConnectionFactoryAccess denied for user。前者说明数据源创建连接失败,后者说明账号或密码不对。先检查 jdbc.properties 中的用户名密码跟本地 MySQL 是否一致,再检查 URL 的 host 和 port;如果改了 MySQL 默认端口,记得在 URL 里同步修改。

MySQL 8.0 用户还会遇到一个独特的报错:java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。这就是前文提到时区参数的原因,解决方式是在连接 URL 上追加serverTimezone=Asia/Shanghai。如果还要消除 SSL 告警,就顺手加上useSSL=false。完整的 URL 写法:

jdbc.url=jdbc:mysql://localhost:3306/bookcorner?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

最后一个容易碰到的问题是驱动 jar 包没打进去。用 IDEA 的 Maven 项目跑无所谓,如果把项目打包成 war 再手动放进 Tomcat 的 webapps 目录,就要去WEB-INF/lib下确认有没有 mysql-connector-java 的 jar。缺少这个 jar 时启动不报错,但一旦有请求触发数据库操作就会报ClassNotFoundException: com.mysql.jdbc.Driver,定位方法是查看部署目录里 lib 文件夹的大小,正常 SSM 项目应该在 20MB 左右,如果只有几百 KB 就是依赖没带全。

5.4 本地能跑和部署到服务器是两个项目

拿到源码后如果只是本地答辩,按照上面的步骤就能跑通。但如果需要部署到云服务器演示,还有一些额外注意点。首先 MySQL 的 root 用户默认只允许 localhost 登录,需要新建用户并授权远程访问,执行GRANT ALL PRIVILEGES ON bookcorner.* TO 'book'@'%' IDENTIFIED BY 'password';。其次防火墙要放行 3306 和 8080 端口。最后 jdbc.properties 里的数据库地址要改成服务器内网 IP 而不是 localhost,因为你的 Java 程序和数据库在同一台机器上,写 localhost 也行,但写公网域名有时会因为安全组规则导致慢连接,稳妥起见用内网 IP。

最后一个建议是运行环境里保留 MySQL 5.7 + JDK 8,因为这个组合与绝大多数源码的依赖版本、驱动写法最匹配。如果本地已经装了 JDK 17 且不方便更换,可以把 pom.xml 中 Spring 的版本升级到 5.3.x、MyBatis 保持 3.5.x、MySQL 驱动换成 8.0.33,但这样做的工作量远比装一个 JDK 8 大,还会引入新旧 API 不兼容的风险。对毕业设计来说,能把一个稳定的组合跑透,比追逐最新版本更有意义。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 15:43:13

open-saas 教程:4 步跑起带 RESTful API 后台的 SaaS 应用

open-saas 教程&#xff1a;4 步跑起带 RESTful API 后台的 SaaS 应用 【免费下载链接】open-saas A 100% free modern JS SaaS boilerplate (React, NodeJS, Prisma). Full-featured: Auth (email, google, github, slack, MS), Email sending, Background jobs, Landing page…

作者头像 李华
网站建设 2026/9/11 15:38:36

树莓派机器人C++与Web控制源码解析及部署实战

简介&#xff1a;这是一份基于树莓派的智能机器人项目源码包&#xff0c;主要面向嵌入式开发初学者、高校学生及毕业设计人员&#xff0c;适用于课程设计、大作业、毕业设计等场景。项目具备较为完整的功能体系&#xff0c;涵盖树莓派机器人硬件控制、图像处理与视觉识别、网络…

作者头像 李华
网站建设 2026/9/11 15:37:43

SpringBoot整合Activiti工作流实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 15:37:34

沈阳房屋鉴定报告到哪里办理?本地正规房屋鉴定公司推荐

在厂房经营、改造、交易、租赁、环评、消防验收等场景中&#xff0c;房屋鉴定报告是非常重要的合规材料。很多业主不清楚报告到底到哪里办理&#xff0c;误以为要跑住建局或不动产登记部门&#xff0c;实际上&#xff0c;正规鉴定报告主要由具备资质的第三方检测机构出具。本文…

作者头像 李华