ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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

简介:这份基于Spring MVC、Spring、MyBatis整合的图书信息管理系统(二)项目包,定位为Java Web学习者与应届毕业生的SSM实战参考,适合课程设计、毕业设计或框架整合练习。压缩包共188个文件,约55.89MB,内含103个jar依赖库、28个xml配置文件、12个Java源文件、12个class编译文件、7个JSP页面和2个SQL脚本。其中jar包保证依赖环境完整,xml与properties负责Spring、MyBatis及数据库连接配置,Java源文件承载控制器与业务逻辑,JSP页面用于前端展示,SQL脚本则提供数据表初始化数据。项目目录清晰,便于导入MyEclipse/Eclipse配合Tomcat、MySQL直接运行对照,该资源已有1119人学习下载。随包另附docx说明文档,对SSM整合流程、图书信息管理中的主要业务功能及增删改查实现思路作了梳理,遇到环境配置或功能实现问题时可快速查阅排错,是快速上手SSM项目开发和同类管理系统设计的实用资料。

1. 项目定位:把 SpringMVC、Spring、MyBatis 缝成一个可运行闭环的图书管理系统

第一次看到这套 SSM 项目时,我第一反应是“怎么又是图书管理”。但把它下载解压、启动 Tomcat 之后,体会完全不一样:SpringMVC 处理请求、Spring 管理对象和事务、MyBatis 操作 MySQL 这三件事,在同一个工程里被安排得明明白白。它不是玩具,而是一个结构完整的图书信息管理系统——图书列表分页展示、按书名/作者组合条件查询、新增、编辑、删除、分类筛选,数据存在 MySQL,视图是 JSP。适用人群非常清晰:刚学完框架理论、想要一份能跑通全流程代码的同学,以及面试前想快速把 SpringMVC、Spring、MyBatis 之间的关系串成一条链的开发者。这套系统最大的价值不在功能多稀奇,而在于它能让你在 WEB-INF、mapper.xml、Spring 配置之间来回跳转时,搞清楚每个文件到底在回答哪个问题。

2. 工程结构拆解:从 web.xml 到 mapper.xml 的调用链长什么样

2.1 三层架构在工程目录里的真实落位

很多初学者装完 SpringMVC、Spring、MyBatis 三份依赖之后,第一个困扰是:这些框架到底怎么分活?答案其实就藏在目录结构里。这个项目的代码按三层切包:controller 包放请求入口,service 包放业务逻辑,mapper 包放数据库交互,entity 包放和表映射的实体类。先看整体目录:

book-system ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/book │ │ │ ├── controller │ │ │ │ └── BookController.java │ │ │ ├── entity │ │ │ │ └── Book.java │ │ │ ├── mapper │ │ │ │ └── BookMapper.java │ │ │ ├── service │ │ │ │ ├── BookService.java │ │ │ │ └── impl │ │ │ │ └── BookServiceImpl.java │ │ │ └── common(分页辅助类、工具类) │ │ ├── resources │ │ │ ├── spring │ │ │ │ ├── spring-mvc.xml │ │ │ │ └── spring-mybatis.xml │ │ │ ├── mybatis │ │ │ │ └── mybatis-config.xml │ │ │ └── mapper │ │ │ └── BookMapper.xml │ │ └── webapp │ │ ├── static │ │ ├── WEB-INF │ │ │ ├── web.xml │ │ │ └── views │ │ │ ├── bookList.jsp │ │ │ └── bookEdit.jsp │ └── sql │ └── init.sql

src/main/resources 下面有个容易看花眼的点:spring 目录放 Spring 容器配置和 SpringMVC 配置,mapper 目录放 MyBatis 的 SQL 映射文件,mybatis 目录放 MyBatis 全局设置。webapp 里有个容易被忽略的细节:JSP 页面放在 WEB-INF/views 下,外部没法通过 URL 直接访问,只能由 Controller 转发进去——这是 JSP 时代比较标准的做法,既防止用户绕过请求直接打开页面,也逼着所有入口过一遍 SpringMVC。

2.2 web.xml:Tomcat 启动时最先碰到的三个入口

SSM 工程里最容易让人一头雾水的文件就是 web.xml。看清它只需要抓住三段配置:ContextLoaderListener 负责启动 spring-mybatis.xml 根容器,CharacterEncodingFilter 处理编码,DispatcherServlet 负责把请求分发到 Controller。下面把核心部分抽出来看:

<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mybatis.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>dispatcherServlet</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>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

load-on-startup 设为 1,意思是 Tomcat 启动时就初始化 DispatcherServlet,而不是等第一个请求来了才加载。把 ContextLoaderListener 和 DispatcherServlet 分别指向两类配置文件,是为了形成 Spring 里的“双亲容器”:父容器包含 Service、Mapper 这些业务和数据对象,子容器包含 Controller。父容器的 bean 对子容器可见,子容器里的 bean 不会反向泄漏。这样 Controller 能注入 service,而 service 不会依赖 controller,分层边界就守住了。

2.3 两套 Spring 配置:一个管业务对象,一个管 Web 请求

理解 SSM 绕不开 spring-mybatis.xml 与 spring-mvc.xml 的职责划分。前者管数据源、SqlSessionFactory、Mapper 扫描、事务管理器;后者管组件扫描 controller、开启注解驱动、配置视图解析器。先看 spring-mybatis.xml:

<context:component-scan base-package="com.example.book"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/book_db?useUnicode=true&amp;characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.book.mapper"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven/>

component-scan 里的 exclude-filter 表示:扫描 com.example.book 下所有要交给 Spring 管理的对象,但把 @Controller 排除出去。Controller 交给 spring-mvc.xml 管理,剩下的 Service、Mapper 实现都进父容器。dataSource 用 DriverManagerDataSource,在中小型项目里最省事,跑起来没问题;生产环境我会换成 Druid 或 HikariCP,但初学阶段没必要让连接池参数先干扰主逻辑。sqlSessionFactory 的 configLocation 指向 MyBatis 全局配置,mapperLocations 指定 SQL 映射文件的位置,启动时 MyBatis 会把 XML 和 Mapper 接口绑起来。tx:annotation-driven 开启后,Service 层写 @Transactional 就能生效。

再看 spring-mvc.xml:

<context:component-scan base-package="com.example.book.controller"/> <mvc:annotation-driven/> <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/"/>

mvc:annotation-driven 是必须的一行:它把处理器映射、参数解析、消息转换这些默认能力全部打开。没有它,Controller 上的 @RequestMapping 不会生效。视图解析器决定了 Controller return "bookList" 时,SpringMVC 会去找 /WEB-INF/views/bookList.jsp。静态资源映射解决的是 CSS、JS 请求被 DispatcherServlet 拦掉的问题,把 /static/ 开头的请求直接交给容器处理。

2.4 依赖版本组合:SSM 为何容易一换就翻车

SSM 项目结构不算复杂,但版本搭配是个容易翻车的地方。我的经验是:Spring 用 5.3.x 系列,spring-webmvc、spring-jdbc、spring-tx 的版本号必须完全一致;MyBatis 用 3.5.x,mybatis-spring 用 2.0.x;数据库驱动换成 mysql-connector-java 8.0.x,对应 MySQL 8.0,驱动类名写 com.mysql.cj.jdbc.Driver。如果照老博客配 Spring 4 + mybatis-spring 1.x + MySQL 5 的老驱动,在新 JDK 上很容易遇到 UnsupportedClassVersionError。pom 里的依赖大体长这样:

<properties> <spring.version>5.3.x</spring.version> <mybatis.version>3.5.x</mybatis.version> </properties> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.x</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.x</version> </dependency>

上面版本里的 x 代表小版本号,实际导入项目时换成本地 Maven 仓库能下载到的最新补丁即可。项目自带的 pom.xml 里已经有现成组合,如果你是自己搭工程,照着这个区间选,基本不会踩版本冲突。

3. 核心模块实战:一次图书查询请求从 Controller 落到 SQL 的完整链路

3.1 实体类和 Mapper 接口:先定数据形状

图书模块最外层的类型是 Book 实体,字段和 tb_book 表一一对应。这个项目的字段命名用了驼峰映射,也就是 bookName 对应 book_name,mybatis-config.xml 里打开 mapUnderscoreToCamelCase 后,查询返回的列会自动映射到实体属性,不需要给每个字段写 resultMap。

public class Book { private Integer id; private String bookName; private String author; private String publisher; private Double price; private String category; // getter、setter 省略,可以用 Lombok 的 @Data 替代 }

BookMapper 是数据访问接口,直接放操作数据库的方法声明。它的方法名要和 BookMapper.xml 里的 SQL id 一一对应,MyBatis 运行时通过 JDK 动态代理帮你实现接口:

public interface BookMapper { List<Book> selectByCondition(Map<String, Object> params); int countByCondition(Map<String, Object> params); Book selectById(Integer id); int insert(Book book); int update(Book book); int deleteById(Integer id); }

Mapper 接口没有实现类,SQL 全在 XML 里。MyBatis 启动时会通过 spring-mybatis.xml 里 MapperScannerConfigurer 的 basePackage 扫描接口,再把 resources/mapper/BookMapper.xml 关联进来。如果接口方法找不到对应 SQL,就会抛 Invalid bound statement,这是后面避坑那节第一个要讲的问题。

3.2 Controller 层:参数绑定、分页参数和视图转发

Controller 是请求链路的第一站。图书列表页需要接收当前页、每页大小、书名和作者筛选条件,SpringMVC 的 @RequestParam 可以给 pageNo 设默认值,Book condition 会把同名请求参数直接绑定到实体对象:

@Controller @RequestMapping("/book") public class BookController { private final BookService bookService; @Autowired public BookController(BookService bookService) { this.bookService = bookService; } @RequestMapping("/list") public String list( @RequestParam(value = "pageNo", defaultValue = "1") Integer pageNo, @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize, Book condition, Model model) { Map<String, Object> params = new HashMap<>(); params.put("pageNo", pageNo); params.put("pageSize", pageSize); params.put("bookName", condition.getBookName()); params.put("author", condition.getAuthor()); List<Book> bookList = bookService.queryBookList(params); int total = bookService.countBook(params); model.addAttribute("bookList", bookList); model.addAttribute("totalPages", (int) Math.ceil(total * 1.0 / pageSize)); model.addAttribute("currentPage", pageNo); model.addAttribute("condition", condition); return "bookList"; } }

这里把分页拆成“列表查询”和“总数统计”两个动作,后端算好 totalPages 再放进 Model,JSP 就能直接生成页码链接。Controller 不做业务计算,只负责收集参数、调 service、把结果放进 Model,然后返回逻辑视图名。视图解析器拿到 bookList 会拼出 /WEB-INF/views/bookList.jsp。

3.3 Service 层:事务注解和分页偏移量的计算

Service 层是业务规则容器,也是 Spring 事务的边界。查询方法标 @Transactional(readOnly = true),增删改标 @Transactional,只要方法里涉及多个 mapper 操作,事务就由这里统一开启和回滚,MyBatis 自己管不了多表写操作的原子性。

@Service public class BookServiceImpl implements BookService { @Autowired private BookMapper bookMapper; @Override @Transactional(readOnly = true) public List<Book> queryBookList(Map<String, Object> params) { int pageNo = (int) params.get("pageNo"); int pageSize = (int) params.get("pageSize"); params.put("offset", (pageNo - 1) * pageSize); return bookMapper.selectByCondition(params); } @Override @Transactional(readOnly = true) public int countBook(Map<String, Object> params) { return bookMapper.countByCondition(params); } @Override @Transactional public boolean addBook(Book book) { if (book.getBookName() == null || book.getBookName().trim().isEmpty()) { return false; } return bookMapper.insert(book) > 0; } }

分页上最容易犯的错是把 pageNo 直接传到 SQL,再在 LIMIT 里做计算。我习惯在 Service 里先把 pageNo、pageSize 换算成 offset,再交给 mapper,SQL 只认 offset 和 pageSize。这样以后换分页组件,SQL 不需要动。业务校验也放在这层:addBook 里判断书名为空就直接返回 false,Controller 再根据返回值决定跳成功页还是失败页。

3.4 mapper.xml 动态 SQL:组合查询和分页的实现重点

MyBatis 容易出彩的地方在 mapper.xml。列表页“书名模糊查 + 作者模糊查 + 分类精确匹配”是典型的动态 SQL 场景,where 标签配合 if 标签能覆盖空与非空的组合。分页用 MySQL 的 LIMIT 加 OFFSET:

<mapper namespace="com.example.book.mapper.BookMapper"> <select id="selectByCondition" resultType="com.example.book.entity.Book"> SELECT id, book_name, author, publisher, price, category, create_time FROM tb_book <where> <if test="bookName != null and bookName != ''"> AND book_name LIKE CONCAT('%', #{bookName}, '%') </if> <if test="author != null and author != ''"> AND author LIKE CONCAT('%', #{author}, '%') </if> <if test="category != null and category != ''"> AND category = #{category} </if> </where> ORDER BY id LIMIT #{pageSize} OFFSET #{offset} </select> <select id="countByCondition" resultType="int"> SELECT COUNT(1) FROM tb_book <where> <if test="bookName != null and bookName != ''"> AND book_name LIKE CONCAT('%', #{bookName}, '%') </if> <if test="author != null and author != ''"> AND author LIKE CONCAT('%', #{author}, '%') </if> <if test="category != null and category != ''"> AND category = #{category} </if> </where> </select> </mapper>

几个关键点:where 标签能自动处理首个子条件多余的 AND,只有 category 有值时,拼出来是 WHERE category = ?,不会出现 WHERE AND category = ?。if 标签里判断的是 Map 的 key,所以 Service 层传参时 key 不能写错。LIKE 用 CONCAT('%', #{bookName}, '%'),不要直接拼 '%${bookName}%',后者存在 SQL 注入风险,条件查询这块很多教程会顺手埋一个雷。count 查询和列表查询的条件必须一致,否则分页总数和列表对不上。

3.5 JSP 视图:c:forEach 渲染表格和操作链接

最后落到 JSP。Controller 已经把 bookList、totalPages、currentPage 放进 Model,JSP 用 JSTL 标签渲染表格。操作列里的删除链接加一个 confirm 弹窗,防止误点:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <table> <tr> <th>编号</th><th>书名</th><th>作者</th><th>出版社</th><th>价格</th><th>操作</th> </tr> <c:forEach items="${bookList}" var="book"> <tr> <td>${book.id}</td> <td>${book.bookName}</td> <td>${book.author}</td> <td>${book.publisher}</td> <td>${book.price}</td> <td> <a href="${pageContext.request.contextPath}/book/toEdit?id=${book.id}">编辑</a> <a href="${pageContext.request.contextPath}/book/delete?id=${book.id}" onclick="return confirm('确定删除该图书吗?')">删除</a> </td> </tr> </c:forEach> </table>

pageContext.request.contextPath 拿到的是应用根路径,避免项目部署成其他名字时链接 404。到这一步,一条“列表页 → Controller → Service → mapper.xml → MySQL → JSP”的完整链路就通了。编辑和删除只是换方法名和 SQL,整体结构完全一致,照着这条链往下加功能就行。

4. 本地跑通与避坑:从 MySQL 初始化到分页列表刷出来的操作路径

4.1 环境要求与建库脚本

跑这个 SSM 工程需要 JDK 8 及以上、Maven 3.6+、Tomcat 8.5 或 9、MySQL 5.7 或 8.0。项目资源里有 sql/init.sql,我按 MySQL 8 的语法写好,直接导入即可。如果本地是 MySQL 5.7,字符集相关内容也兼容,差别不大:

DROP TABLE IF EXISTS tb_book; CREATE TABLE tb_book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), publisher VARCHAR(100), price DECIMAL(10,2), category VARCHAR(30), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO tb_book (book_name, author, publisher, price, category) VALUES ('后端开发实践', '示例作者A', '示例出版社', 79.00, '计算机'), ('数据库系统原理', '示例作者B', '示例出版社', 99.00, '计算机'), ('分布式系统入门', '示例作者C', '示例出版社', 45.00, '计算机');

价格字段我用了 DECIMAL,不要用 FLOAT 或 DOUBLE 存金额,不然价格会有一堆浮点尾巴。create_time 用数据库默认值,Java 代码里新增图书时不用手动塞时间。建库命令:

mysql -uroot -p < src/main/resources/sql/init.sql

如果脚本路径和工程分离,也可以先登录 MySQL 再 source:

mysql -uroot -p source /你的路径/init.sql;

Windows 下 source 路径不要用反斜杠,容易报文件找不到。

4.2 修改配置、打包、部署到 Tomcat

数据库就绪后,需要改 spring-mybatis.xml 里三样东西:url 后面的库名、username、password。url 里的 useUnicode=true 和 characterEncoding=utf-8 保持不变,这是 MySQL 中文编码的后悔药,删掉之后乱码会找上门。

IDEA 里跑最省事,步骤就几步:

  1. 用 IDEA 打开 pom.xml,等 Maven 依赖下载完成。
  2. 在 Project Structure 的 Artifacts 里确认 war exploded 已生成。
  3. 配置 Tomcat Server,Deployment 里添加这个工程,Application context 设为 /book。
  4. 启动后访问 http://localhost:8080/book/book/list。

其中第二个 book 是 Controller 的 @RequestMapping 前缀。不用 IDEA 的话,命令行也能跑:

mvn clean package -DskipTests cp target/book-system.war $CATALINA_HOME/webapps/ $CATALINA_HOME/bin/startup.sh

Tomcat 会自动解压 war,访问地址变成 http://localhost:8080/book-system/book/list。如果 context 路径变了,JSP 里的 pageContext.request.contextPath 会自动适配,静态资源不会 404。

4.3 避坑:SSM 启动阶段最常见的五个问题

坑一:项目启动就报 Invalid bound statement,mapper 方法找不到 SQL。

现象是控制台抛 org.apache.ibatis.binding.BindingException,提示某个方法找不到。原因是 MapperScannerConfigurer 的 basePackage 写错,或者 mapperLocations 没匹配到 XML,也可能是 XML 的 namespace 和接口全限定名不一致。解决:先核对 spring-mybatis.xml 里 basePackage 是否等于接口所在包,再打开 BookMapper.xml 确认根节点 namespace 写的是 com.example.book.mapper.BookMapper 完整类名。

坑二:Tomcat 启动时报 Access denied for user 'root'@'localhost',连接池创建失败。

原因很直接:数据库密码填错,或者 MySQL 8 的 root 账户默认走 caching_sha2_password 认证,而驱动版本太旧无法认证。解决:先在命令行用 mysql -uroot -p 验证能否登录;再把驱动升到 mysql-connector-java 8.0.x,driverClassName 同步写成 com.mysql.cj.jdbc.Driver。

坑三:页面传中文条件查询,结果是乱码或查不到数据。

原因分两段:POST 请求体乱码是 web.xml 的 CharacterEncodingFilter 没生效,GET 请求参数乱码是 Tomcat 按 ISO-8859-1 解析查询串。解决:web.xml 里确认 encodingFilter 对 /* 生效;GET 乱码还要去 Tomcat 的 server.xml,给 Connector 加 URIEncoding="UTF-8";同时检查 spring-mybatis.xml 的 url 里 characterEncoding=utf-8 和表字符集一致。

坑四:列表页能开,但点“编辑”链接跳 404。

原因是浏览器地址栏发的是 GET 请求,Controller 里 toEdit 方法如果只写了 @PostMapping,就匹配不上。解决:把方法注解改成 @RequestMapping(value = "/toEdit", method = RequestMethod.GET),确认视图解析器拼出的 JSP 路径真实存在,就能定位。

坑五:Tomcat 报 UnsupportedClassVersionError,或者 Spring 的 NoSuchMethodError。

原因是编译用的 JDK 版本和运行 Tomcat 的 JDK 版本不一致,或者 Spring 相关包版本号没对齐。解决:把 IDEA 的 Maven JRE 设置成和 Tomcat 一致的 JDK;检查 pom 里 spring-webmvc、spring-jdbc、spring-tx 的版本号必须完全相同,mybatis-spring 和 mybatis 的版本对应关系也要固定。

这些问题我基本都踩过一遍,按上面方式排查,通常几分钟到半小时就能定位。最后一条经验:看到异常先看完整堆栈的第一行业务异常,不要反复重启瞎试。

5. 二次开发思路:从图书 CRUD 扩到借阅业务要动哪几层

借阅是图书管理最自然的扩展,给这个系统增加“借阅记录”模块,动作就是完全顺着第 3 章的链路补一层。先在数据库加一张 tb_borrow 表,包含 borrow_id、book_id、reader_name、borrow_time、return_time,表之间不建物理外键,用逻辑关联就行。然后新建 BorrowRecord 实体和 BorrowRecordMapper 接口,在 XML 里放一段 join 查询把书名带出来:

public interface BorrowRecordMapper { List<BorrowRecord> selectJoinBook(Map<String, Object> params); int insert(BorrowRecord borrowRecord); }
<select id="selectJoinBook" resultType="com.example.book.entity.BorrowRecord"> SELECT b.borrow_id, b.book_id, bk.book_name, b.reader_name, b.borrow_time, b.return_time FROM tb_borrow b LEFT JOIN tb_book bk ON b.book_id = bk.id ORDER BY b.borrow_time DESC </select>

BorrowRecord 实体里要加一个字符串字段 bookName 来接 join 出来的书名,这就是最简单的 VO 写法。Service 层同样加 @Transactional,Controller 增加 /borrow/list 方法,JSP 复制 bookList 改成借阅展示。新增借阅表单就提交到 /borrow/add,逻辑和 addBook 完全对称。

如果想把列表接口改成给前端 Ajax 用,只需要在 Controller 加一个返回 JSON 的入口。保留原来的 JSP 跳转,同时增加:

@RequestMapping("/listJson") @ResponseBody public Map<String, Object> listJson(@RequestParam Integer pageNo, @RequestParam Integer pageSize) { Map<String, Object> result = new HashMap<>(); Map<String, Object> params = new HashMap<>(); params.put("pageNo", pageNo); params.put("pageSize", pageSize); result.put("code", 0); result.put("count", bookService.countBook(params)); result.put("data", bookService.queryBookList(params)); return result; }

这个 Map 会被 Jackson 序列化成 JSON,前端表格组件直接消费,以后想接分页插件或者换成表格组件都容易。@ResponseBody 的作用是关键:SpringMVC 不再走视图解析器,而是把返回值直接写回响应体。

需要提醒的是:不要看到新需求就埋头写代码,先把“建表 → 实体 → Mapper → XML → Service → Controller → 页面”这条链走一遍,确认和现有模块一一映射再动手。我之前给类似系统加借阅排行时,因为跳过了实体设计,直接把统计 SQL 塞进 BookMapper,导致 Book 查出来的行和排行榜对象对不上,返工花了一个下午。从那以后,我每次加模块都强制把所有层走一遍,宁可多写一个 VO 类也不偷懒。希望帮到你。

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

返回列表