资讯详情 SSM图书管理系统源码实战:从Spring MVC到MyBatis完整整合教程
📅 2026/10/23 16:50:12
简介这套基于SSMSpring MVC Spring MyBatis的图书信息管理系统项目包面向正在学习Java Web框架整合、需要完成课程设计或毕业设计的开发者。项目围绕图书信息管理实现了控制层、业务层和持久层代码可在MyEclipse或Eclipse中结合Tomcat与MySQL运行技术栈既有SSM整合方式也保留了JSPServletJavaBeanJDBC的实现思路适合作为SSM入门到进阶的参考模板。压缩包共包含188个文件整体大小约55.89MB。其中103个jar依赖包提供了SSM框架运行所需的第三方库28个xml配置文件覆盖Web部署描述、Spring容器装配和MyBatis映射等关键配置12个Java源码与12个class文件展示控制层、业务层、持久层的分层写法7个JSP页面构成前端交互界面另有2个SQL脚本用于初始化数据库表结构1个docx文档可作为项目说明参考。目前已有1119人学习下载对框架初学者有较强实用性。通过这套资源读者能够获得一套结构完整的SSM项目模板学习请求流转、接口映射、事务整合等核心知识点同时可参考配置文件与SQL脚本快速搭建自己的后台管理系统节省从零搭建环境的时间。1. 图书信息管理系统二一个能把 SSM 流程完整串起来的项目包A同学前几天拿着一份图书管理系统源码来找我调 bug登录页面一点提交就 500。我看了下控制台是 Spring MVC 包扫描路径写错了Controller 根本没被注册。这种问题在刚接触 SSM 组合的开发手里太常见了。这个基于 Spring MVC Spring MyBatis 的图书信息管理系统二源码包是把三者协作关系理清的一套完整范例登录、读者管理、图书管理、借还记录都齐全。适合刚学完框架想找个真实项目练手的人也适合要交课程设计的在校生拿来改造成自己的系统。下面我会从架构分工讲到配置、数据库、复现步骤最后落到一个加分项的改造。2. SSM 三件套怎么协作从 URL 到数据库的完整链路很多人在单独学 Spring、Spring MVC、MyBatis 时觉得都懂一合起来就晕。这个项目的价值就在于它把三者分工摆得很清楚。先看职责再看调用链之后配置文件才能看懂。2.1 谁在管请求、谁在管业务、谁在管 SQL这套系统是标准的 SSM 三层结构。Spring MVC 只管 Web 层接收 HTTP 请求根据 URL 找到对应的 Controller 方法把前端提交的参数绑定成 Java 对象最后把返回的 ModelAndView 交给视图解析器渲染成 JSP。Spring 容器管的是 Service 层的 BeanController 里用 Autowired 注入 Service 接口实际对象由容器创建事务也在 Service 层生效。MyBatis 管的是数据访问Mapper 接口定义方法XML 文件写 SQL运行时由 MyBatis 生成实现类把 ResultSet 映射成实体。一次完整的登录请求链路是这样的前端提交表单 → DispatcherServlet 截获 → HandlerMapping 匹配 /login 到 LoginController 的 login() 方法 → Controller 调用 UserService.findUserByUsername() → UserService 注入 UserMapper → UserMapper 对应 UserMapper.xml 里的 select 语句执行 → MyBatis 将数据库记录映射成 User 对象 → 返回给 Service → Controller 判断密码后跳转页面。这个链路理清了后面看项目代码就不会迷路。2.2 项目包结构一张图看懂代码放哪打开源码包先看包结构。这个项目没有用 Maven 多模块就是单模块 Web 工程包名是 com.bookms下面分成五个子包com.bookms ├── common │ ├── Page.java // 分页对象 │ └── Result.java // 统一返回结果封装 ├── controller │ ├── BookController.java │ ├── ReaderController.java │ ├── BorrowController.java │ └── LoginController.java ├── mapper │ ├── BookMapper.java │ ├── ReaderMapper.java │ └── BorrowMapper.java ├── pojo │ ├── Book.java │ ├── Reader.java │ └── Borrow.java └── service ├── BookService.java ├── ReaderService.java ├── BorrowService.java └── impl ├── BookServiceImpl.java └── ...这里的 pojo 对应数据库表每个字段对应表里的列MyBatis 通过 resultType 自动映射。controller 层只做参数接收和逻辑转发不写 SQLservice 接口和 impl 分离事务注解写在 impl 上。按这个分层去加功能会顺手很多。我一般拿到分包不清晰的 SSM 项目第一件事就是按这个骨架重新归类否则后面 MyBatis 的 mapper 扫描会出问题。2.3 核心配置文件web.xml、spring-mvc.xml、spring-mybatis.xmlSSM 项目里配置文件是重灾区。这个项目一共有三个核心 XML各自管一件事。先看 web.xmlweb-app version3.0 display-namebookms/display-name filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/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-mapping servlet servlet-namespringMvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringMvc/servlet-name url-pattern//url-pattern /servlet-mapping /web-app这里的关键是 DispatcherServlet 的 url-pattern 配成/表示所有请求都交给 Spring MVC 处理但这样静态资源css、js、图片也会被拦截所以等会儿在 spring-mvc.xml 里必须放行静态资源。CharacterEncodingFilter 必须在最前面不然中文乱码。load-on-startup 设为 1让容器启动时就初始化 Spring 容器。接着看 spring-mvc.xmlbeans xmlnshttp://www.springframework.org/schema/beans xmlns:mvchttp://www.springframework.org/schema/mvc xmlns:contexthttp://www.springframework.org/schema/context xsi:schemaLocation... context:component-scan base-packagecom.bookms.controller/ mvc:annotation-driven/ mvc:default-servlet-handler/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean /beanscomponent-scan 只扫描 controller 包Service 和 Mapper 不在这里注册它们归 Spring 根容器管。annotation-driven 是开启 ResponseBody、RequestBody 等注解的必要项这个漏了接口会全部报 415 或 404。default-servlet-handler 就是放行静态资源的加上之后浏览器才能加载登录页的 css。然后是管数据源的 spring-mybatis.xmlbeans xmlnshttp://www.springframework.org/schema/beans xmlns:contexthttp://www.springframework.org/schema/context xsi:schemaLocation... context:property-placeholder locationclasspath:db.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.bookms.pojo/ property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ /bean /property /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.bookms.mapper/ /bean /beansmapperLocations 把 mapper 包下的 XML 都加载进去MapperScannerConfigurer 扫描 Mapper 接口生成的实现类由 Spring 管理。这一步很容易漏漏了就会在 Service 注入时直接启动失败。mapUnderscoreToCamelCase 设为 true 后数据库里的 publish_date 就能自动映射成 publishDate少写很多 resultMap。注意这个项目用的是 Druid 连接池如果你本机没配 Druid 依赖也可以换成 CommonsDbcp2但要注意 driverClassName 要跟着换否则数据源初始化会报 ClassNotFoundException。3. 数据库设计与 MyBatis 映射图书、读者、借阅记录三张表的联动一个图书管理系统最核心的不是页面是数据表之间怎么约束。这个项目一共有三张主表图书表、读者表、借阅记录表。理解了这三张表整个系统的业务逻辑就透了一半。3.1 建表语句与字段设计打开资源包里的 db/bookms.sql里面是完整的建表和测试数据。核心建表语句长这样CREATE TABLE book ( book_id INT AUTO_INCREMENT PRIMARY KEY, book_name VARCHAR(100) NOT NULL, isbn VARCHAR(20) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), publish_date DATE, total_count INT DEFAULT 1, lend_count INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1可借 0不可借 ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE reader ( reader_id INT AUTO_INCREMENT PRIMARY KEY, reader_name VARCHAR(50) NOT NULL, phone VARCHAR(20), password VARCHAR(64) NOT NULL, max_borrow INT DEFAULT 5 COMMENT 最大借书数量 ) ENGINEInnoDB DEFAULT CHARSETutf8; CREATE TABLE borrow_record ( borrow_id INT AUTO_INCREMENT PRIMARY KEY, book_id INT NOT NULL, reader_id INT NOT NULL, borrow_time DATETIME, return_time DATETIME, status TINYINT DEFAULT 0 COMMENT 0借出 1已还 2逾期, KEY idx_book (book_id), KEY idx_reader (reader_id) ) ENGINEInnoDB DEFAULT CHARSETutf8;注意 borrow_record 里有两个外键字段但没有直接写 FOREIGN KEY 约束。这个项目是在 Service 层手动校验读者是否存在、图书是否可借外键约束写在 SQL 里反而会让测试数据插入顺序变麻烦。生产系统我会加物理外键课程项目或者小系统用逻辑外键更灵活。字段类型上日期用 DATE借还时间用 DATETIME不要拿字符串存日期否则后面统计逾期会很痛。3.2 Mapper 接口与 XML动态 SQL 在哪用MyBatis 的 Mapper 接口只有方法签名真正的 SQL 写在 mapper/BookMapper.xml 里。看一下 BookMapper.java 和对应 XMLpublic interface BookMapper { ListBook searchBooks(Param(name) String name, Param(status) Integer status, Param(offset) int offset, Param(limit) int limit); int countBooks(Param(name) String name, Param(status) Integer status); int insertBook(Book book); int updateStatus(Param(bookId) int bookId, Param(status) int status); }select idsearchBooks resultTypecom.bookms.pojo.Book SELECT book_id, book_name, isbn, author, publisher, publish_date, total_count, lend_count, status FROM book where if testname ! null and name ! AND book_name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND status #{status} /if /where ORDER BY book_id DESC LIMIT #{offset}, #{limit} /select这里用了where和if组成动态 SQL。搜索页面允许按书名模糊查也允许按状态精确查两个条件可空可填。where标签会自动去掉第一个多余的 AND避免你拼 SQL 时还要做字符串判断。参数用#{}MyBatis 会预编译成?防止 SQL 注入。这个习惯必须要养成项目里所有参数传入都用#{}不要用${}直接拼表名或字段名。countBooks 和 searchBooks 的查询条件必须保持一致。很多翻车现场就是列表查出来 10 条分页总条数却对不上原因就是两条 SQL 的 where 条件不一致。我每次写这种查询都会先复制条件再改查询字段降低漏条件的概率。3.3 resultType 和 resultMap什么时候必须用后者这个项目的 book 表字段是下划线风格而 Book 实体是驼峰风格。前面配置里开了 mapUnderscoreToCamelCase所以大部分查询可以直接用 resultType 映射。但如果你的 SQL 里有 JOIN 查询比如查看借阅记录时要同时显示书名和读者名就得用 resultMap 或者给查询列起别名public class BorrowVO extends Borrow { private String bookName; private String readerName; }select idselectBorrowList resultTypecom.bookms.vo.BorrowVO SELECT b.borrow_id, b.borrow_time, b.return_time, b.status, bk.book_name, r.reader_name FROM borrow_record b LEFT JOIN book bk ON b.book_id bk.book_id LEFT JOIN reader r ON b.reader_id r.reader_id WHERE b.status #{status} ORDER BY b.borrow_time DESC /select这里 resultType 因为别名刚好和 VO 属性名一致所以还能直接用。如果名字对不上比如数据库叫 publishDate 而实体叫 publishTime就必须写 resultMap 手动映射。我建议在 SSM 项目里先确认别名实在不方便别名再上 resultMap可读性会好很多。4. 复现流程从空目录到跑通登录要改的四个地方源码包导入 IDE 后会遇到一堆环境问题。这里按我实际跑通的顺序说清楚每一步要做什么、为什么要做。4.1 环境版本选择别在版本上硬踩坑这个项目基于 MavenJDK 1.8 或 11 都行Tomcat 8.5 或 9 比较稳。MySQL 5.7 和 8.0 都能跑但连接配置不同。如果本机装的 MySQL 8.0pom.xml 里驱动版本至少是 8.0.20 以上并且 db.properties 里要写成jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/bookms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456MySQL 5.7 的话driver 可以用 com.mysql.jdbc.Driver但没必要8.x 驱动兼容 5.7所以直接用上面这份即可。serverTimezone 是 MySQL 8 强制要用的参数不写会报时区错误。useSSLfalse 是避免驱动默认尝试 SSL 握手时报警告。4.2 导入源码包并核对 pom 依赖用 IDEA 或 Eclipse 导入 Maven 项目后先看 pom.xml 是否包含这几个核心依赖dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependencyspring-webmvc 会连带引入 spring-context、spring-core 等基础包所以不用逐个声明。mybatis-spring 是 SSM 整合的关键没有它 SqlSessionFactory 没法注册成 Spring Bean。servlet-api 必须设为 provided否则 Tomcat 会和你 war 包里的版本冲突启动时可能出现多个 ServletContainerInitializer 的报错。4.3 建库建表并启动 Tomcat在 MySQL 里执行资源包中的 db/bookms.sql我用命令行演示mysql -uroot -p123456 db/bookms.sql执行完确认一下数据use bookms; show tables; select * from reader;然后启动 Tomcat。如果是在 IDEA 里配置好 Tomcat Server 后把 project 的 deployment 设为 war explodedApplication context 填/bookms直接 Run。也可以用命令行方式先打出 war 包再部署mvn clean package -DskipTests cp target/bookms.war $CATALINA_HOME/webapps/ sh $CATALINA_HOME/bin/startup.sh启动日志里看到SpringMVC Servlet已经在/bookms注册完成就可以访问curl -I http://localhost:8080/bookms/login返回 200 就说明跑通了。如果返回 404优先检查 spring-mvc.xml 里的视图解析器前缀是不是 /WEB-INF/jsp/以及 JSP 文件到底在不在那个目录下。4.4 系统典型操作登录、查书、借书打开登录页输入初始管理员账号 admin / 123456进入主页。图书管理页会调/book/list?page1limit10返回当前页图书列表支持按书名搜索。借书操作流程是先查读者→查图书→确认借出实际上会依次调用/borrow/add接口传入 readerId 和 bookId。这套代码里没有使用 pagehelper分页是手写 LIMIT 实现的所以你在改分页时要记得传 offset 和 limit 两个参数。前端页面上点击下一页会重新拼 URL 传给后端后端按当前页码计算 offset。5. 避坑与排查复现 SSM 项目时最常见的五个报错说点血泪经验。我帮 A同学调这个系统时遇到的几个问题几乎是 SSM 新手必经的坑。下面按现象、原因、解决三步写。5.1 启动后访问登录页直接 404现象Tomcat 启动成功但访问 http://localhost:8080/bookms/login 返回 404 页面。 原因DispatcherServlet 的 url-pattern 配成/后默认把对 JSP 的请求也拦截了而 Spring MVC 找不到对应的 Handler也没有配置默认放行静态资源。 解决在 spring-mvc.xml 里补上mvc:default-servlet-handler/或者显式配置mvc:resources映射静态目录。如果是视图解析器路径不对还要检查 JSP 是否放在 /WEB-INF/jsp 下且前缀写的是 /WEB-INF/jsp/。5.2 登录页能打开提交时报数据库连接失败现象填写登录信息后点击登录页面显示 500 错误控制台报 Communications link failure 或者 Access denied for user。 原因第一种是 MySQL 的认证方式与驱动的兼容问题第二种是密码写错。最常见的是连接 URL 里没写 serverTimezone导致连接无法建立。 解决把 db.properties 的 URL 改成上面那串带 serverTimezone 和 useSSLfalse 的完整地址如果账号密码确认无误就在 MySQL 里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456;刷新认证方式。注意这行命令只适用 MySQL 8MySQL 5.7 一般不需要。5.3 Service 调用 Mapper 时报 Invalid bound statement not found现象系统启动正常但进入一个查询接口后报 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)看起来 Mapper 方法存在XML 也有。 原因mapperLocations 配置没有匹配到 XML 文件。常见是 XML 文件放在 src/main/java 包下但项目没有把 xml 打包进 target/classes或者 spring-mybatis.xml 里写的路径是classpath:mapper/*.xml而 XML 实际在其它目录。 解决检查 target/classes 下是否有 mapper 目录和 XML如果没有在 pom.xml 的 build 里加resources把src/main/java/**/*.xml也纳入打包。确认 spring-mybatis.xml 的 mapperLocations 路径与实际包路径一致。5.4 插入图书后数据库里中文乱码现象页面输入中文书名保存后 MySQL 里显示???或者繁体乱码。 原因Tomcat 请求编码没设 UTF-8且连接 URL 里也没有 characterEncodingutf8。Spring 的 CharacterEncodingFilter 虽然配置了但只对 POST 请求的表单参数生效如果 GET 请求携带中文Tomcat 默认读 ISO-8859-1照样乱码。 解决web.xml 里确保 encodingFilter 的 url-pattern 是/*MySQL 建库时指定 utf8db.properties 的 URL 里保留 characterEncodingutf8。修改后重启 Tomcat再清一次浏览器缓存。5.5 借书成功但借阅记录看不到读者姓名现象借书流程不报错但借阅列表里读者姓名和书名都是 null。 原因BorrowVO 里只有 bookName、readerName 两个字段但 MyBatis 查询结果列名是 reader_name开启驼峰映射后可以转成 readerName前提是 Mapper XML 中 select 没有显式指定 resultMap 覆盖这个映射。此外如果 SQL 里 LEFT JOIN 的表别名不对也可能选不到数据。 解决检查 Mapper XML 里 SQL 字段别名是否和 VO 属性完全对应或者直接给查询列写别名如r.reader_name AS readerName。尽量不要在 JOIN 查询里复用 resultType 配 resultMap容易两边不一致。6. 进阶给借阅流程加一个读者借阅上限校验这个系统读者表里预留了 max_borrow 字段默认是 5但代码里没有真正用它做拦截。把它落到 Service 层就是一个很好的改造点。先在 BorrowService 接口加一个方法int borrowBook(int bookId, int readerId);然后在 BorrowServiceImpl 里实现校验逻辑Override Transactional public int borrowBook(int bookId, int readerId) { Reader reader readerMapper.selectById(readerId); if (reader null) { throw new RuntimeException(读者不存在); } int borrowed borrowMapper.countUnreturned(readerId); if (borrowed reader.getMaxBorrow()) { throw new RuntimeException(当前已借 borrowed 本已达上限 reader.getMaxBorrow() 本); } Book book bookMapper.selectById(bookId); if (book null || book.getStatus() 0) { throw new RuntimeException(图书不存在或不可借); } int row borrowMapper.insertBorrow(bookId, readerId); if (row 1) { bookMapper.increaseLendCount(bookId); } return row; }注意我用了 Transactional这样校验和插入保持在同一事务里如果后面 bookMapper.increaseLendCount 失败整个借书操作回滚不会出现借阅记录插入成功但库存数量没变的脏数据。业务规则放 Service 而不是 Controller是因为以后如果加了手机端接口同样可以复用这套校验不会因为换入口就绕过限制。验证时可以先造一个已借 4 本的读者再借第 6 本观察是否正确抛出异常。如果希望更优雅可以把抛 RuntimeException 的地方改成一个自定义 BusinessException上层统一捕获后转成 JSON 返回给前端。我以前做类似功能时习惯把校验塞在 Controller 里结果后来换了套前端页面逻辑没法复用只能加班重构。从那以后我每次往系统里加规则都强制先落到 Service 层再考虑谁调用它。希望这个 SSM 项目的改造思路也能帮你少走一段弯路。本文还有配套的精品资源点击获取