简介:这份资源面向正在学习或准备课程设计、毕业设计的高校学生与Java Web初学者,提供一套基于Spring MVC + Spring + MyBatis(SSM)框架的图书信息管理系统完整工程,帮助读者理解分层架构、数据库映射与前后端交互的落地方式,也可作为JSP + Servlet + JavaBean + JDBC传统方案的对照参考。压缩包共188个文件,约55.89MB,以103个jar依赖包、28个xml配置、12个java源码与12个class编译文件为主,另含7个jsp页面、6个properties配置、2个sql脚本及项目工程文件,覆盖从依赖、配置到业务代码与建库脚本的完整链路。已有1120人学习下载。读者可从中获得可直接导入MyEclipse/Eclipse并部署到Tomcat的工程结构,结合MySQL建表脚本快速跑通系统,并借助Controller、Service、Mapper等分层代码理解SSM整合要点与常见配置排错思路。
1. 接手一个 SSM 图书管理系统,我第一件事是看它怎么跑起来
朋友把一份图书信息管理系统(二).zip甩过来,说“你帮我看看这个能不能跑”。我解压一看,典型的 SSM 三层架构:Spring MVC 接请求、Spring 管 Bean 和事务、MyBatis 写 SQL 映射,数据库是 MySQL。这种项目在培训班和课程设计里出现频率极高,但真正能一次跑通的不多——不是缺 jar 包,就是web.xml里DispatcherServlet配错,再不然就是 MyBatis 的mapper路径对不上。
这份资源适合两类人:一是刚学完 SSM 想找个完整项目练手的,二是手里有类似代码但跑不起来、想对照排查的。它不是什么高并发架构,就是一个标准的图书增删改查系统,但正因为标准,拿来理解 SSM 的请求链路和分层职责反而最直接。下面我按“先跑通、再看结构、最后改功能”的顺序拆一遍。
2. 环境搭建与启动:从解压到浏览器出页面
2.1 先确认技术栈和依赖版本
这种 SSM 项目最怕版本打架。我一般先看pom.xml(如果是 Maven 项目)或者WEB-INF/lib下的 jar 包。典型组合是 Spring 4.x 或 5.x、MyBatis 3.4 左右、MySQL 驱动 5.1.x 或 8.x、JDK 8。如果项目里没有pom.xml,说明是传统 Web 工程,jar 包全在lib目录,那就得手动确认有没有spring-webmvc、mybatis-spring、mysql-connector-java、druid或c3p0连接池。
我习惯先把版本对齐再动手,因为 MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver,而 5.x 是com.mysql.jdbc.Driver,URL 还要加时区和 SSL 参数。这一步不确认,后面启动必报Communications link failure。
<!-- pom.xml 里我一般会锁死这几个版本,避免传递依赖乱跳 --> <properties> <spring.version>5.3.20</spring.version> <mybatis.version>3.5.9</mybatis.version> <mysql.version>8.0.28</mysql.version> </properties>上面这段不是项目原文,是我接手时习惯补的版本锁定。参数说明:spring.version控制整个 Spring 生态,mybatis.version要和mybatis-spring匹配,mysql.version决定驱动类名和连接串写法。如果原项目用的是 5.1 驱动,就别硬升到 8,改连接串比改驱动省事。
2.2 数据库建库和连接配置
图书管理系统的表结构一般就三四张:book(图书)、category(分类)、user(管理员)、可能还有borrow(借阅记录)。我先找项目里的.sql文件,没有的话就看实体类字段反推。建库语句通常是CREATE DATABASE library DEFAULT CHARACTER SET utf8mb4;,然后导入表结构和几条测试数据。
连接配置在jdbc.properties或applicationContext.xml里。我见过太多项目把密码写成root/123456但没在文档里说,跑的时候一脸懵。所以第一步是把这几个值改成自己本机的:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码参数说明:serverTimezone不写会报时区错误,useSSL=false在本地开发可以关掉避免证书警告,characterEncoding=utf8保证中文书名不乱码。改完这四个值,连接池基本就能初始化。
2.3 Tomcat 部署和启动检查
传统 Web 工程丢进 Tomcat 的webapps目录,或者 IDEA 里配 Artifact 和 Tomcat Server。启动后先看控制台有没有BeanCreationException,再看DispatcherServlet有没有映射成功。我一般直接访问http://localhost:8080/项目名/,如果出 404 但控制台没报错,八成是web.xml里url-pattern写成了/但视图解析器前缀不对。
<!-- web.xml 里 DispatcherServlet 的典型配置 --> <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:springmvc.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>这段配置的意思是:所有请求交给 Spring MVC 处理,springmvc.xml里再配注解扫描和视图解析器。load-on-startup为 1 表示容器启动就初始化,方便早发现配置错误。如果这里写成*.do,那访问路径就得带.do后缀,和 Controller 的@RequestMapping要对上。
3. 目录结构与分层职责:一个请求怎么落到 SQL
3.1 标准 SSM 的包结构长什么样
解压后src下一般是这么分的:controller、service、service.impl、mapper(或dao)、entity、util。resources下放springmvc.xml、applicationContext.xml、mybatis-config.xml、jdbc.properties和mapper/*.xml。webapp下是WEB-INF/jsp或pages,加静态资源css/js。
这个分层不是摆设。Controller 只负责接参数、调 Service、返回视图名;Service 管业务逻辑和事务;Mapper 接口配 XML 写 SQL。我见过有人把 SQL 直接写在 Service 里用JdbcTemplate,那就不是 SSM 了。看一个项目规不规范,先看 Controller 里有没有@Autowired注入了 Mapper——有的话说明分层被穿透了。
3.2 从 Controller 到 Mapper 的调用链
拿图书列表来说,请求/book/list进来,BookController接住,调BookService.findAll(),Service 再调BookMapper.selectAll(),MyBatis 把 XML 里的select执行完映射成List<Book>,返回给 JSP 渲染。整条链路里,事务注解@Transactional一般加在 Service 实现类的方法上,不在 Controller 也不在 Mapper。
@Controller @RequestMapping("/book") public class BookController { @Autowired private BookService bookService; @RequestMapping("/list") public String list(Model model) { // 查询全部图书,塞进模型给 JSP 遍历 List<Book> books = bookService.findAll(); model.addAttribute("books", books); return "book/list"; // 对应 /WEB-INF/jsp/book/list.jsp } }逻辑说明:@RequestMapping("/book")是类级别前缀,方法上的/list拼起来就是/book/list。Model是 Spring MVC 提供的模型容器,addAttribute的 key 在 JSP 里用${books}取。返回值"book/list"会被视图解析器拼成/WEB-INF/jsp/book/list.jsp,所以前缀后缀在springmvc.xml里必须配对。
3.3 MyBatis 映射文件的关键写法
Mapper XML 里最容易翻车的是namespace和resultMap。namespace必须等于 Mapper 接口的全限定名,否则启动报Invalid bound statement。字段名和属性名不一致时,要么开mapUnderscoreToCamelCase,要么手写resultMap。
<mapper namespace="com.example.mapper.BookMapper"> <select id="selectAll" resultType="com.example.entity.Book"> SELECT id, name, author, price, category_id AS categoryId FROM book ORDER BY id DESC </select> </mapper>参数说明:resultType走的是自动映射,category_id AS categoryId是为了让下划线字段对上驼峰属性。如果项目里字段多、关联多,建议改成resultMap显式声明,别依赖自动映射的玄学行为。ORDER BY id DESC让新书排前面,这是产品习惯,不是技术必须。
4. 核心模块拆解:增删改查里的业务细节
4.1 图书新增和表单校验
新增图书看着简单,但参数绑定和校验是坑最多的地方。Spring MVC 用@ModelAttribute或直接对象接参,日期字段要配@DateTimeFormat,价格字段用BigDecimal别用double。我一般会在 Controller 里加BindingResult接校验结果,避免校验失败直接 400。
@RequestMapping(value = "/add", method = RequestMethod.POST) public String add(@Validated Book book, BindingResult result, Model model) { if (result.hasErrors()) { // 校验失败回到表单页,保留已填内容 model.addAttribute("book", book); return "book/add"; } bookService.insert(book); return "redirect:/book/list"; }逻辑说明:@Validated触发实体类上的@NotBlank、@NotNull等注解,BindingResult必须紧跟在被校验对象后面,否则 Spring 直接抛异常。redirect:前缀表示重定向,避免刷新页面重复提交。参数上method = RequestMethod.POST限定只接 POST,防止 GET 误触。
4.2 删除和批量删除的 SQL 写法
单条删除用DELETE FROM book WHERE id = #{id},批量删除用foreach拼IN。这里要注意#{}和${}的区别:#{}是预编译占位符,防注入;${}是字符串拼接,只在动态表名、排序字段这种场景用,且必须白名单校验。
<delete id="deleteBatch"> DELETE FROM book WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </delete>参数说明:collection="ids"对应 Mapper 接口里@Param("ids") List<Integer> ids的名字,open/separator/close拼出(1,2,3)。如果接口没加@Param,单参数集合默认叫list或array,名字对不上就报Parameter 'ids' not found。
4.3 分页查询的两种落地方式
小项目常用 PageHelper 插件,PageHelper.startPage(pageNum, pageSize)紧跟在查询前一行,返回的List实际是Page对象。另一种是手写LIMIT,在 XML 里传offset和pageSize。前者省事但依赖插件,后者可控但要自己算总数。
public PageInfo<Book> findPage(int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<Book> list = bookMapper.selectAll(); return new PageInfo<>(list); }逻辑说明:startPage只对下一次查询生效,中间不能插别的 SQL。PageInfo封装了总页数、总记录数、是否首页尾页,JSP 里直接取。注意selectAll的 XML 里不要自己写LIMIT,否则插件会再包一层导致语法错误。
5. 避坑与排查:启动报错和页面 500 的常见原因
5.1 报Invalid bound statement (not found)
现象:启动不报错,一访问列表就 500,日志里说找不到 Mapper 方法。原因通常是三个:Mapper XML 没被扫描到、namespace写错、XML 文件名和接口名不一致。解决:在applicationContext.xml里配mapperLocations为classpath:mapper/*.xml,确认namespace是全限定接口名,且 XML 放在resources/mapper下能被编译进classes。
5.2 中文乱码从哪一层来
现象:表单提交的书名在数据库里是问号,或者页面显示乱码。原因分三层:JSP 页面没写pageEncoding="UTF-8"、web.xml 没配CharacterEncodingFilter、数据库连接串缺characterEncoding=utf8。解决:三处都补上,Filter 的forceEncoding设为 true,且放在所有 Filter 最前面。
5.3 事务不生效的静默失败
现象:Service 里两条更新,第一条成功第二条抛异常,但第一条没回滚。原因多半是@Transactional加在接口上而不是实现类上,或者方法被同类内部调用绕过了代理。解决:注解加到 Service 实现类的 public 方法上,applicationContext.xml里开<tx:annotation-driven/>,且确保数据源的事务管理器配的是DataSourceTransactionManager。
5.4 静态资源被 DispatcherServlet 拦截
现象:页面能出但 CSS/JS 全 404。原因:url-pattern配了/,静态资源也进了 Spring MVC。解决:在springmvc.xml里加<mvc:resources mapping="/css/**" location="/css/"/>和<mvc:default-servlet-handler/>,把静态资源放行给默认 Servlet。
6. 二次开发改造:从能跑到能用的几个切入点
这份项目跑通之后,真正有价值的是拿它练手改造。我一般从三个方向动:一是把 JSP 换成 Thymeleaf 或前后端分离的 JSON 接口,Controller 返回值改成@ResponseBody,这一步能逼你把视图解析器和消息转换器搞清楚;二是给图书表加个stock库存字段,借阅时扣减,用@Transactional保证扣库存和写借阅记录在一个事务里,顺便验证事务到底生不生效;三是把jdbc.properties里的明文密码挪到环境变量,连接池换成 Druid 并开监控页,看看 SQL 执行时间。
改造时我有个习惯:每改一个模块,先把原来的接口用 Postman 或浏览器跑一遍留个基线,改完再对比返回结构。比如列表接口原来返回 JSP,改成 JSON 后字段名不能变,否则前端全挂。验证方法很简单——改完启动,先看控制台有没有NoSuchBeanDefinitionException,再逐页点新增、删除、分页,最后看数据库里数据对不对。
有个血泪教训:我曾在BookMapper.xml里把resultType写成resultMap但没定义对应resultMap,启动不报错,一查询就空指针,查了半小时才发现是标签用错。从那以后我每次改 Mapper XML,都强制先跑一遍单表查询再动关联查询,避免把简单问题拖成连环坑。希望这份拆解能帮你少走几步弯路,把这份 SSM 项目真正跑起来、改下去。
本文还有配套的精品资源,点击获取