简介:这是一套面向Java Web初学者与课程设计者的SSM框架实战项目资源,完整实现图书借阅全流程管理,适用于高校Java开发实训、毕业设计参考及Spring生态入门学习。资源包含基于Spring+SpringMVC+MyBatis构建的后台系统、LayUI前端界面及MySQL数据库脚本,涵盖借阅/还书、图书/读者/类型/公告/管理员六大核心模块,并集成统计分析功能,管理员默认账号为admin/123456。压缩包共919个文件(44.35MB),含40个Java业务类、75个JSP页面、123个JS交互脚本、45个CSS样式文件及39个XML配置文件,另有Druid连接池、Tomcat部署WAR包及SQL建库脚本,目录结构清晰,Controller/Service/DAO分层明确,便于理解SSM整合逻辑与MVC职责划分。目前已有840人学习下载,可直接导入IDEA运行,快速掌握企业级图书管理系统开发全流程。 图书管理系统是我接过的课程设计里最经典的题目,没有之一。这次用SSM(Spring + SpringMVC + MyBatis)把这套系统从零到一搭起来,源码和数据库我都整理好了。如果你是准备做JavaWeb课程设计、毕业设计,或者想搞明白SSM框架到底怎么整合,这篇文章值得你花二十分钟看完。
整套系统麻雀虽小五脏俱全:管理员登录、图书信息管理、借书还书、分页查询、条件检索、数据库初始化脚本,全部包含。项目底层用了Spring做对象的统一管理,SpringMVC负责请求转发和数据绑定,MyBatis负责和MySQL打交道,这是很多Java后端岗位面试都会问到的经典组合。文章会拆解设计思路、数据库建表、核心配置、常见报错,最后直接告诉你源码和数据库怎么跑起来。
1. 这套图书管理系统的整体设计与技术选型
1.1 为什么选SSM而不是Spring Boot
很多人会问,现在新项目都上Spring Boot了,为什么还折腾SSM?我的看法是:课程设计和毕业设计阶段,SSM依然值得做。SSM把“配置”这件事摊开放在你面前,你能看到Spring容器怎么装配、SpringMVC怎么拦截请求、MyBatis怎么把接口映射成SQL,踩过一遍配置的坑,再回头用SpringBoot,你会知道它到底帮你省了什么。
用一个类比来解释这三个组件:Spring是总调度室,所有对象的创建和依赖关系都由它管理;SpringMVC是前台接待,用户发来的请求先到它这里,再由它分发给对应的Controller;MyBatis是仓库管理员,负责把数据库里的数据搬进项目,也把内存里的对象写回数据库。三者各管一段,职责清晰。
我选择SSM的另一个原因,是它对Tomcat的部署方式非常传统,能练到“手动打war包、手动部署Web容器”这些基础操作。现在很多同学用完Spring Boot内嵌Tomcat,反而不知道应用是怎么部署到外部服务器上的,这在实际工作中其实很容易吃亏。
1.2 系统功能模块拆解
这套图书管理系统的业务需求并不复杂,核心就三类角色操作:管理员、图书、借还记录。但功能模块划分得越清楚,后面写代码越不容易乱。我按“先主数据、后业务流水”的方式拆分:
- 用户登录模块:管理员和普通读者都走同一张用户表,登录成功后把用户信息放进Session,配一个拦截器统一校验未登录访问。
- 图书管理模块:图书的增删改查,列表页支持分页,支持按书名、作者、分类做模糊查询。
- 读者管理模块:维护读者信息,包括姓名、联系方式、借书状态,管理员可以新增、禁用读者账号。
- 借阅管理模块:管理员为读者办理借书,生成借阅记录,同时扣减图书库存;还书时更新归还时间,恢复库存。
- 基础统计模块:首页展示图书总量、读者总量、未归还记录数等统计值,方便管理员一眼掌握核心数据。
这里需要注意,我把“借书”和“还书”设计成两条独立的Service方法,而不是在一个方法里用状态字段硬切。原因是借书要校验库存、要创建借阅记录、要扣库存;还书要校验是否有未还记录、要更新返回时间、要加库存。两个操作的事务边界不同,混在一起会非常难维护。
1.3 目录结构与分层设计
源码的包结构如果不提前规划,项目一变大就回天乏术。我采用标准的Maven Web工程分层,包名统一以com.example.library开头:
library-ssm ├── pom.xml ├── src/main/java/com/example/library │ ├── controller # SpringMVC控制器 │ ├── service # 业务接口 │ │ └── impl # 业务实现 │ ├── mapper # MyBatis Mapper接口 │ ├── entity # 实体类 │ ├── interceptor # 登录拦截器 │ └── common # 公共类、分页结果封装 ├── src/main/resources │ ├── mapper # MyBatis XML文件 │ ├── spring # Spring主配置、SpringMVC配置 │ └── jdbc.properties # 数据库连接配置 └── src/main/webapp ├── static # 静态资源 └── WEB-INF/views # JSP页面Controller只负责接收参数、调用Service、封装结果返回页面;Service只处理业务逻辑,不直接写SQL;Mapper接口只做数据库操作声明,SQL写在XML里;entity就是数据库表结构对应的JavaBean。这套分层的好处是,后续想加一个“图书导出”功能,只需要在Controller和Service里各加一段代码,不需要动数据库访问层。
2. 数据库设计与初始化
2.1 核心表结构设计
数据库是整个系统的地基,设计阶段多花一小时,后面少加三天班。我在这里只保留了三张核心表:用户表、图书表、借阅记录表。三张表之间的关系很清晰:一个读者可以借多本图书,一本图书可以被多个读者借阅,借阅记录表就是它们之间的关联表。
先看用户表,我把它命名为t_user,用来存放管理员和读者两种账号:
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL COMMENT '登录名', `password` varchar(128) NOT NULL COMMENT '登录密码,MD5加密后保存', `real_name` varchar(64) DEFAULT NULL COMMENT '真实姓名', `role` int(1) DEFAULT '0' COMMENT '角色:0-管理员 1-读者', `status` int(1) DEFAULT '1' COMMENT '状态:1-正常 0-禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;图书表t_book我放了几个关键业务字段,数量和价格都用明确语义的命名,避免使用count、number这种没有业务含义的词:
CREATE TABLE `t_book` ( `id` int(11) NOT NULL AUTO_INCREMENT, `isbn` varchar(32) DEFAULT NULL COMMENT 'ISBN编号', `book_name` varchar(128) NOT NULL COMMENT '书名', `author` varchar(64) DEFAULT NULL COMMENT '作者', `publisher` varchar(128) DEFAULT NULL COMMENT '出版社', `price` decimal(10,2) DEFAULT '0.00' COMMENT '定价', `category` varchar(32) DEFAULT NULL COMMENT '分类,如Java、文学', `stock` int(11) DEFAULT '0' COMMENT '库存数量', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_book_name` (`book_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;借阅记录表t_borrow是关键业务流水,必须记录借书人、图书、时间、状态:
CREATE TABLE `t_borrow` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '读者ID', `book_id` int(11) NOT NULL COMMENT '图书ID', `borrow_time` datetime DEFAULT NULL, `return_time` datetime DEFAULT NULL, `status` int(1) DEFAULT '0' COMMENT '状态:0-借出中 1-已归还', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_book_id` (`book_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有个设计细节:日期时间字段我统一用datetime,不用timestamp。原因很简单,MySQL的timestamp有2038年问题,虽然现在的数据量远远到不了那一刻,但课程设计场景下datetime更稳定、最好理解,排序和范围查询也都支持得很好。
2.2 初始化数据与SQL脚本导入
数据库脚本我建议按顺序执行,先建库,再建表,最后插入测试数据。测试数据不要太多,但一定要够演示分页功能,所以我往图书表里插了20本左右不同类型的书,用户表里插了管理员和读者账号。
初始化SQL文件保存为library.sql,里面包含完整的建库建表语句和INSERT语句。导入方法有两种:
命令行方式:
mysql -uroot -p source /你的路径/library.sql;Navicat方式更快:新建连接后,右键连接选择“运行SQL文件”,选择library.sql直接执行。执行完成后,在数据库中应能看到t_user、t_book、t_borrow三张表和数据。
我建议在正式导数据之前,先用SELECT * FROM t_user;确认一下表是否创建成功。宁可多执行一次验证,也不要等到项目启动时报错再回头查数据库。
2.3 数据库连接配置的坑
SSM项目的数据库连接配置集中在jdbc.properties文件里,我会在部署环节详细说,但这里先提醒最关键的三个参数:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=123456第一,useSSL=false一定要加。MySQL 5.7及以上版本如果不显式关闭SSL,控制台会频繁打警告,甚至导致连接超时。第二,characterEncoding=utf8必须加,否则插入中文数据后页面会显示“??”或者乱码。第三,如果本机MySQL是8.x,驱动类要改成com.mysql.cj.jdbc.Driver,同时url里还要加时区参数serverTimezone=Asia/Shanghai,这是我从MySQL5切到MySQL8时踩过的最实在的坑。
3. 从零搭建SSM框架的核心配置
3.1 Maven依赖与版本选择
SSM整合的依赖版本不能乱配,尤其是Spring、MyBatis、MyBatis-Spring三者之间,版本不兼容会直接导致程序启动失败。下面是我验证过的一套版本组合:
| 依赖 | 版本 |
|---|---|
| spring-context / spring-webmvc / spring-jdbc | 5.3.23 |
| mybatis | 3.5.13 |
| mybatis-spring | 2.1.2 |
| mysql-connector-java | 5.1.49 |
| pagehelper | 5.3.3 |
| jackson-databind | 2.13.4 |
| c3p0 | 0.9.5.5 |
pom.xml里的核心依赖这样写:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.23</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.4</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> </dependencies>还要注意,Maven默认编译级别可能跟JDK不匹配,建议在pom里显式指定JDK版本:
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>否则你用IDEA的JDK17跑项目,会遇到“无效的目标发行版”之类的编译错误。
3.2 Spring/SpringMVC/MyBatis整合配置
SSM整合的关键在于:Spring负责装配Service、Mapper和事务,SpringMVC负责Controller层,两者虽然都在同一个web容器里,但配置文件要分开。
先看applicationContext.xml,这是Spring的主配置:
<context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" 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:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.example.library.entity"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.library.mapper"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>再写spring-mvc.xml,只扫描Controller,不扫描Service和Mapper,避免同一个Bean被创建两次:
<mvc:annotation-driven/> <context:component-scan base-package="com.example.library.controller"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:resources mapping="/static/**" location="/static/"/>最后在web.xml里配置Spring的上下文参数、SpringMVC的DispatcherServlet、以及编码过滤器:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/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> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>这里容易踩的坑是url-pattern。如果你写成*.do,那所有不带.do的请求都会404;写成/时,静态资源必须靠<mvc:resources>放行,否则页面样式全部丢失。
3.3 登录模块与拦截器实现
登录模块是系统的入口,我把它放在Controller最前面实现,主要代码流程是:
@Controller public class UserController { @Autowired private UserService userService; @PostMapping("/login") public String login(String username, String password, HttpSession session, Model model) { User user = userService.login(username, MD5Utils.md5(password)); if (user == null) { model.addAttribute("msg", "用户名或密码错误"); return "login"; } session.setAttribute("loginUser", user); return "redirect:/book/list"; } @RequestMapping("/logout") public String logout(HttpSession session) { session.invalidate(); return "redirect:/login.jsp"; } }我这里对密码做了MD5加密后再去数据库比对,这是最基础的防护,至少能让明文密码不会直接存在数据库里。真正的生产环境肯定要用BCrypt加盐,但课程设计阶段用MD5+固定盐足够演示,代码也简单。
拦截器我单独建了一个LoginInterceptor类,继承HandlerInterceptor,重写preHandle方法:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("loginUser") != null) { return true; } response.sendRedirect(request.getContextPath() + "/login.jsp"); return false; } }然后在SpringMVC配置里注册拦截器,放行登录页面和静态资源:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/login.jsp"/> <mvc:exclude-mapping path="/static/**"/> </mvc:interceptor> </mvc:interceptors>3.4 图书借阅的事务管理
借书操作涉及两步数据库操作,必须放在一个事务里:先插入借阅记录,再更新图书库存。如果中途某一步失败,数据库会处于“记录已插入但库存没扣”的错误状态。我直接在Service实现类上加了注解:
@Service public class BorrowServiceImpl implements BorrowService { @Autowired private BorrowMapper borrowMapper; @Autowired private BookMapper bookMapper; @Transactional(rollbackFor = Exception.class) @Override public void borrowBook(Long userId, Long bookId) { Book book = bookMapper.findById(bookId); if (book == null || book.getStock() <= 0) { throw new RuntimeException("图书不存在或库存不足"); } Borrow borrow = new Borrow(); borrow.setUserId(userId); borrow.setBookId(bookId); borrow.setBorrowTime(new Date()); borrow.setStatus(0); borrowMapper.insert(borrow); bookMapper.decreaseStock(bookId); } }这里@Transactional(rollbackFor = Exception.class)一定不能简写成@Transactional。默认情况下,Spring事务只在抛出运行时异常时才回滚,如果代码抛的是SQLException这类受检异常,事务不会回滚,那数据就脏了。
另一个注意点:事务要放在Service层,不要放到Controller。Controller离业务太近容易把事务范围扩大,导致长事务占用数据库连接,并发高的时候很容易拖垮连接池。
4. 实操过程:图书CRUD与分页查询的实现细节
4.1 图书管理页面的整体流程
图书管理是整个系统的核心功能,我以“图书列表页”为例,走一遍完整的请求流程:
- 浏览器访问
/book/list?pageNum=1&keyword=Java。 - DispatcherServlet根据映射找到
BookController。 - Controller调用
BookService的pageQuery方法。 - Service先调
PageHelper.startPage(pageNum, pageSize),再调BookMapper.selectList(keyword)。 - MyBatis执行XML里的SQL,返回
Page<Book>。 - Controller把
PageInfo封装进Model,转发到book/list.jsp。 - JSP使用JSTL和EL遍历数据,渲染表格和分页栏。
业务流程里最值得学习的是“分页查询”和“条件查询”如何组合,我在下文单独展开。
4.2 分页查询与条件检索
分页我用了PageHelper这个插件,好处是不用手写LIMIT和总记录数统计。用法很简单:
public PageInfo<Book> pageQuery(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); List<Book> books = bookMapper.selectList(keyword); return new PageInfo<>(books); }但这里有个非常隐蔽的坑:PageHelper.startPage只对紧跟着它的第一条SQL生效。如果你在调用Mapper之前,先查了一次用户、或者遍历了一个集合,分页就会失效。所以我在Controller里只是拿到参数直接传给Service,在Service里必须保证startPage和selectList之间不要夹任何其他数据库操作。
对应的Mapper XML:
<select id="selectList" resultType="com.example.library.entity.Book"> SELECT id, isbn, book_name, author, publisher, price, category, stock, create_time FROM t_book <where> <if test="keyword != null and keyword != ''"> AND (book_name LIKE CONCAT('%', #{keyword}, '%') OR author LIKE CONCAT('%', #{keyword}, '%') OR category LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY id DESC </select>这里条件拼接用了<where>和<if>,MyBatis会自动去掉多余的AND,避免写出“WHERE 1=1”这种临时方案,SQL看着更整洁。模糊查询用CONCAT('%', #{keyword}, '%'),绝对不能直接写LIKE '%${keyword}%',因为${}是字符串拼接,存在SQL注入风险。
4.3 图书新增与编辑的表单参数绑定
新增图书的表单是POST提交,表单字段名和Book实体属性名相同,SpringMVC会自动绑定:
<form action="${pageContext.request.contextPath}/book/add" method="post"> <input type="text" name="isbn" > <input type="text" name="bookName" > <input type="text" name="author" > <input type="text" name="publisher" > <input type="number" name="price" step="0.01"> <input type="text" name="category" > <input type="number" name="stock" > <button type="submit">新增</button> </form>Controller的写法:
@PostMapping("/add") public String add(Book book) { bookMapper.insert(book); return "redirect:/book/list"; }编辑和新增复用一个JSP页面是最常见的做法,我用一个bookEdit.jsp,页面上加载时判断book.id是否为空,为空就显示“新增”,不为空就显示“编辑”,提交地址动态切换为/book/update。这里要注意,修改操作必须携带id,否则会更新到一条不存在的记录,甚至报空指针。
还有一个细节:删除操作我建议用POST,不要用GET。因为GET请求被搜索引擎、预加载工具访问后,如果链接里带删除ID,可能误删数据。虽然课程设计没有这么严格,但养成用POST处理写操作的习惯是好的。
4.4 JSP前端的几个细节处理
JSP页面虽然传统,但有一些特别影响体验的细节。
第一,页面顶部要用<%@ page contentType="text/html;charset=UTF-8" language="java" %>,并且最好在每个页面引入JSTL标签库,否则EL表达式$ {}在Tomcat低版本下可能不生效。
第二,项目根路径要动态获取。在JSP里尽量不要写死/library/xxx,否则部署时改了上下文路径,所有链接都会404。我是在页面顶部写一行:
<c:set var="ctx" value="${pageContext.request.contextPath}"/>之后所有链接都写成${ctx}/book/list。
第三,列表页要处理“没有数据”的情况。我用<c:forEach>遍历数据,配合<c:if test="${empty pageInfo.list}">显示“暂无数据”的提示行,比直接显示空表格强很多。
分页栏我做的比较简单,上一页、下一页、总数、当前页/总页数。用PageInfo自带的属性:
当前第 ${pageInfo.pageNum} 页 / 共 ${pageInfo.pages} 页 / 共 ${pageInfo.total} 条 <a href="${ctx}/book/list?pageNum=${pageInfo.prePage}&keyword=${keyword}">上一页</a> <a href="${ctx}/book/list?pageNum=${pageInfo.nextPage}&keyword=${keyword}">下一页</a>这里有个容易遇到的问题:翻页时如果带了keyword,分页链接里必须把它带到下一页,否则用户搜完“Java”后翻到第二页,条件就丢了,看到的会变成全部图书。
5. 常见问题与排查技巧实录
5.1 启动和部署问题
SSM项目跑不起来,80%的问题集中在Maven依赖、Tomcat部署、Spring扫描三个层面。我把自己踩过和帮别人排查过的典型问题整理成了速查表:
| 报错或现象 | 最常见原因 | 处理建议 |
|---|---|---|
ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet | 依赖未下载或未打包进WAR | 检查pom中spring-webmvc是否引入;IDEA中执行mvn clean package,确认lib目录有jar包 |
Failed to configure a DataSource | jdbc.properties没被加载 | 确认applicationContext.xml里有context:property-placeholder,且文件路径正确 |
java.lang.NoClassDefFoundError: org/springframework/jdbc/datasource/... | 缺少spring-jdbc依赖 | 加上spring-jdbc依赖后重新导入 |
| 访问项目显示404且控制台无日志 | 项目没有正确部署到Tomcat | 检查IDEA的Artifacts和Deployment配置,确认application context设置为/library |
| IDEA提示“源服务器找不到目标资源” | 部署时没选“war exploded” | 最好用war exploded,修改JSP不用重新打包 |
我建议所有Maven依赖拉完之后,先执行一次mvn clean package,看打包是否成功。如果打包成功,项目基础配置基本没问题;如果打包时就有红色报错,优先看编译错误,别急着启动Tomcat。
5.2 数据库连接与中文乱码问题
数据库连接问题通常分两类:一类是连不上,一类是连上了但数据错乱。
连不上的典型报错是Access denied for user 'root'@'localhost'或者Communications link failure。前者是账号密码错,后者一般是端口不对、MySQL没启动、或者url写错。最笨但有效的方法:先用数据库客户端连一次MySQL,能连上再让项目连。
如果数据库客户端能连,项目连不上,重点检查jdbc.properties里的driverClass和jdbcUrl。MySQL5.7和8.x的驱动类不一样,前面已经提过。这个坑我印象很深,有一次项目换到MySQL8,漏加了serverTimezone,启动报了一长串时区错误,最后在url后面拼上&serverTimezone=Asia/Shanghai才解决。
中文乱码分三个层面解决:
- 数据库层面:建表时指定
DEFAULT CHARSET=utf8mb4,数据导入时确认连接字符集是utf8。 - 连接层面:jdbc.url中加上
characterEncoding=utf8。 - Web层面:web.xml配置
CharacterEncodingFilter,JSP页面contentType="text/html;charset=UTF-8"。
这三个地方缺一不可。我见过有人只改了JSP,页面显示正常,但插入数据库后还是乱码,就是因为漏了jdbc.url的编码参数。
5.3 框架配置与代码问题速查
除了启动问题,运行时最烦人的几个报错我也单独列出来:
| 报错 | 原因 | 处理 |
|---|---|---|
Invalid bound statement (not found) | Mapper接口没有找到对应的XML,或XML的namespace不对 | 检查mapperLocations是否指向mapper目录,XML里namespace是否等于接口全限定名 |
No qualifying bean of type 'BookService' | Service实现类没加@Service,或没被扫描 | 检查context:component-scan扫描的包是否包含service包 |
Could not autowire. No beans of type 'BookMapper' | Mapper接口没有扫描到 | 检查MapperScannerConfigurer的basePackage是否正确 |
| PageHelper分页无效 | 插件没注册,或startPage后不是紧接查询语句 | 在SqlSessionFactoryBean里配置插件;把PageHelper.startPage放到Mapper调用之前一行 |
HTTP 405 Request method 'POST' not supported | 控制器只写了@GetMapping,但表单用了POST提交 | 把方法改成对应的RequestMethod |
property "stock" not found on object | JSP属性名和实体属性名不一致 | 检查实体类有没有getStock()方法,或者是否加了Lombok注解 |
如果用的是IDEA,我建议启动时把日志级别调到DEBUG,至少先看到MappingThe request URL这样的信息,能快速判断请求是否进到了Controller。
6. 源码导入与数据库初始化实战步骤
6.1 源码导入IDEA的完整流程
拿到源码后不要急着双击启动。我先说一套稳妥的导入流程,适用于最新的IDEA版本:
- 打开IDEA,选择
File -> New -> Project from Existing Sources,选中源码根目录,选择Maven项目。 - 等待右下角Maven依赖下载。这一步如果长时间卡住,先检查自己本地的Maven仓库
settings.xml是否配置了阿里云镜像,没有的话大概率下载得很慢。 - 依赖下载完成后,先不要运行,打开
Project Structure,确认Project SDK选择的是本机JDK8或更高版本,Language level设置为8。 - 在
Project Structure -> Modules里把项目标记为Web项目:选中模块,点“+”添加Web,配置Web Resource Directory指向src/main/webapp。
我遇到过的典型问题是项目导入后没有Maven“刷新”按钮,或者代码全部爆红但报错信息又不明确。这种时候最简单有效的办法是:右键pom.xml -> Maven -> Reload project,等左下角进度条走完,大部分爆红都会消失。
6.2 初始化数据库并修改配置文件
数据库初始化前面提过,用Navicat或命令行执行library.sql即可。执行后重点检查三张表是否建好、管理员账号是否存在。
接着修改src/main/resources/jdbc.properties,把三样改成你本机的实际值:jdbc.username、jdbc.password、jdbc.url。如果你的MySQL在本地,端口3306没变,只有用户名密码需要改。
修改完成后,我建议先写一个简单的测试方法,用Spring的JdbcTemplate或者直接启动项目到登录页,验证数据库连接没问题。千万不要等到运行到借书功能才发现数据库没连上,到那时候排查成本就高很多了。
6.3 部署到Tomcat并验证功能
在IDEA里配置Tomcat非常简单,但有一个选项要特别注意。
选择Run -> Edit Configurations,点击“+”,选Tomcat Server -> Local。在Deployment标签页,点击“+”,选择Artifact...,然后选library-ssm:war exploded,Application context我填的是/library。
Tomcat端口设置成 8080,与MySQL不相干,两个服务可以同时运行。启动之前确认Tomcat自身没有报端口占用,如果8080被占用,可以在Server标签页改成8081。
启动成功后,浏览器访问:
http://localhost:8080/library/login.jsp输入源码里初始化的管理员账号,应该能进入系统后台,看到首页统计数字、图书列表、分页按钮。到这一步,整套基于SSM的图书管理系统就真正跑起来了。
我个人建议,如果你是第一次跑SSM项目,先不要急着改功能,按原样跑通一遍,再在图书管理模块加一个“导出Excel”之类的小功能练手。这套源码里的配置都可以直接复用。我在实际部署中踩得最多的还是Tomcat应用上下文路径和数据库时区两个问题,把这两点记住,基本就顺了。
本文还有配套的精品资源,点击获取