当前位置: 首页 > news >正文

SSM框架从零搭建图书管理系统:Spring+SpringMVC+MyBatis整合实战

简介:这是一套面向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我放了几个关键业务字段,数量和价格都用明确语义的命名,避免使用countnumber这种没有业务含义的词:

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_usert_bookt_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-jdbc5.3.23
mybatis3.5.13
mybatis-spring2.1.2
mysql-connector-java5.1.49
pagehelper5.3.3
jackson-databind2.13.4
c3p00.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 图书管理页面的整体流程

图书管理是整个系统的核心功能,我以“图书列表页”为例,走一遍完整的请求流程:

  1. 浏览器访问/book/list?pageNum=1&keyword=Java
  2. DispatcherServlet根据映射找到BookController
  3. Controller调用BookServicepageQuery方法。
  4. Service先调PageHelper.startPage(pageNum, pageSize),再调BookMapper.selectList(keyword)
  5. MyBatis执行XML里的SQL,返回Page<Book>
  6. Controller把PageInfo封装进Model,转发到book/list.jsp
  7. 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里必须保证startPageselectList之间不要夹任何其他数据库操作。

对应的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 DataSourcejdbc.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里的driverClassjdbcUrl。MySQL5.7和8.x的驱动类不一样,前面已经提过。这个坑我印象很深,有一次项目换到MySQL8,漏加了serverTimezone,启动报了一长串时区错误,最后在url后面拼上&serverTimezone=Asia/Shanghai才解决。

中文乱码分三个层面解决:

  1. 数据库层面:建表时指定DEFAULT CHARSET=utf8mb4,数据导入时确认连接字符集是utf8。
  2. 连接层面:jdbc.url中加上characterEncoding=utf8
  3. 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 objectJSP属性名和实体属性名不一致检查实体类有没有getStock()方法,或者是否加了Lombok注解

如果用的是IDEA,我建议启动时把日志级别调到DEBUG,至少先看到MappingThe request URL这样的信息,能快速判断请求是否进到了Controller。

6. 源码导入与数据库初始化实战步骤

6.1 源码导入IDEA的完整流程

拿到源码后不要急着双击启动。我先说一套稳妥的导入流程,适用于最新的IDEA版本:

  1. 打开IDEA,选择File -> New -> Project from Existing Sources,选中源码根目录,选择Maven项目。
  2. 等待右下角Maven依赖下载。这一步如果长时间卡住,先检查自己本地的Maven仓库settings.xml是否配置了阿里云镜像,没有的话大概率下载得很慢。
  3. 依赖下载完成后,先不要运行,打开Project Structure,确认Project SDK选择的是本机JDK8或更高版本,Language level设置为8。
  4. 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.usernamejdbc.passwordjdbc.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应用上下文路径和数据库时区两个问题,把这两点记住,基本就顺了。

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

http://www.cnnetsun.cn/news/4329570.html

相关文章:

  • Python漫画爬虫实战:接口解析、并发下载与zip打包全攻略
  • 2026深圳工程建筑材料检测排名 TOP5 CMA 资质提供钢材检测、水泥检测、砂石检测 全覆盖联系方式推荐
  • 2026沈阳工程建筑材料检测排名 TOP5 CMA 资质提供钢材检测、水泥检测、砂石检测 全覆盖联系方式推荐
  • go-zero电商后台脚手架Zero-Admin:从框架原理到二次开发实践
  • Python+分布式架构:基于CARLA的自动驾驶仿真系统解析
  • aigc率检测和论文查重有什么区别?看懂报告后再决定怎样AI降重?
  • ops-nn Tiling策略详解:张量切分与并行调度的核心机制和3大交付件
  • 幻影防务 MPX 3.0 安装全指南:从环境检查到启动验证
  • AI生成内容加水印:技术原理、落地挑战与可信互联网的基石
  • 从1946到2023:zip压缩包解压与数据修复全记录
  • Kronos:基于预训练+微调的金融K线时序预测模型实战指南
  • 植物叶片分割数据集构建与模型调优实战:从0.62到0.87
  • 垃圾分类运输路径优化:从VRP建模到遗传算法实战
  • Claude Code编排机械臂实现物理拦截:从风控决策到仿真执行
  • VMware Workstation 16.1.2 安装、虚拟机创建、去虚拟化与彻底卸载指南
  • Umi-OCR 快速上手:免费离线OCR软件,3步搞定截图、批量与PDF识别
  • 为什么值得试试 Ghostty:一款快速、跨平台、GPU 加速终端的实践指南
  • FBG反射谱与透射谱的Matlab仿真:从传输矩阵到调参避坑
  • ai-memory用Copilot和OpenAI OAuth:零API Key配置订阅账号指南
  • 3分钟部署Shannon:Docker跑通AI渗透测试环境
  • RMA距离徙动算法详解:从原理到Matlab成像实现
  • 51单片机节日彩灯控制器设计与Proteus仿真实战
  • stitch-skills stitch-loop完全指南:一个提示词构建多页网站
  • YOLOv8-Pose驾驶员疲劳检测系统实战:从数据标注到UI界面
  • 国内B2C电商脱敏数据集:设计思路、脱敏方案与实战场景
  • STM32+MAX30102实现心率血氧检测与OLED显示实战
  • Matlab调用SBDART做辐射传输计算:从原理到实战全解析
  • MATLAB调用SBDART辐射传输模型:从解压到批量跑通
  • Zabbix与Prometheus监控体系实战:从部署到告警全解析
  • 2018迅雷计算机视觉校招笔试解析:基础考点与备考经验