尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Mybatis+Thymeleaf构建购书商城:从架构到部署的实战指南

SpringBoot+Mybatis+Thymeleaf构建购书商城:从架构到部署的实战指南 简介这是一套基于SpringBootMybatisThymeleafMySQL实现的完整购书商城系统源码面向Java Web初学者与全栈开发入门者解决在线图书选购、订单管理及后台运营等核心电商场景落地问题。资源包共109个文件涵盖35个Java业务与实体类含Controller、Service、Mapper、10个Thymeleaf HTML页面如product.html、cart.html、9个JS交互脚本、8个CSS样式文件含weui.css、bootstrap.min.css等移动端适配样式、6个XML配置与映射文件、1个application.yml及1个初始化SQL脚本整体压缩包仅4.81MB结构清晰、依赖精简、开箱即用。已有50人学习下载适合用于课程设计、毕业项目或Spring技术栈综合实践。读者可直接导入IDE运行完整体验用户注册登录、图书浏览/搜索/分类、购物车增删改查、下单支付模拟、订单管理及后台商品/用户/订单维护等全流程功能并基于现有架构快速集成Spring Security权限控制、Elasticsearch搜索优化或第三方支付接口。1. 项目背景与核心价值为什么选择这套技术栈最近在整理过往项目时翻出了一个基于 SpringBoot Mybatis Thymeleaf MySQL 开发的购书商城系统。这个项目虽然不算新颖但它的技术组合非常经典可以说是 Java Web 开发领域一个“教科书式”的入门到进阶的练手项目。很多朋友在自学后端开发时都会尝试搭建一个类似的商城系统但往往在技术选型、模块划分和细节实现上踩坑无数。今天我就把这个项目的完整实现思路、核心代码逻辑以及我趟过的那些“坑”系统地梳理一遍希望能给正在学习或准备搭建类似系统的你提供一个清晰、可落地的参考。为什么是这套技术栈这背后其实有很强的现实考量。SpringBoot 提供了“开箱即用”的快速启动能力让你能跳过繁琐的 XML 配置专注于业务逻辑。Mybatis 作为一款半自动化的 ORM 框架在 SQL 的灵活性和对象映射的便利性之间取得了很好的平衡尤其适合需要复杂查询或对 SQL 有精细化控制的中小型项目。Thymeleaf 作为服务端模板引擎语法自然能与 SpringBoot 无缝集成非常适合用来渲染动态的 HTML 页面。而 MySQL 作为最流行的开源关系型数据库其稳定性和社区支持毋庸置疑。这套组合拳打下来既能保证开发效率又能满足一个商城系统在数据一致性、交易处理和页面展示上的基本需求是经过大量项目验证的“黄金搭档”。2. 项目架构设计与模块划分一个清晰的架构是项目成功的基石。这个购书商城系统虽然功能完整但结构并不复杂我采用了典型的分层架构确保各司其职便于维护和扩展。2.1 整体技术架构图景整个系统可以划分为四个核心层次表现层 (Presentation Layer)由 Thymeleaf 模板引擎负责。它接收来自控制层的模型数据并渲染成最终的 HTML 页面返回给用户的浏览器。Thymeleaf 的模板文件.html存放在src/main/resources/templates/目录下。控制层 (Controller Layer)基于 Spring MVC 的Controller或RestController。它负责处理 HTTP 请求调用相应的业务逻辑并准备模型数据传递给视图或者直接返回 JSON 数据对于前后端分离的接口本项目主要使用前者。业务逻辑层 (Service Layer)这里封装了所有的核心业务规则和流程。例如用户注册的逻辑校验、购物车的商品计算、订单的创建与状态流转等。Service 层依赖于数据访问层。数据访问层 (DAO Layer)由 Mybatis 的 Mapper 接口和对应的 XML 映射文件组成。它负责与 MySQL 数据库进行交互执行增删改查CRUD操作。数据库方面我设计了几张核心表user用户、book图书、category图书分类、cart_item购物车项、order订单、order_item订单明细。表结构的设计遵循了数据库三大范式的基本思想同时为了查询性能在订单表等高频查询字段上适当添加了索引。2.2 包结构规划与Maven依赖在项目的src/main/java下我建立了清晰的包结构com.example.bookstore ├── controller // 控制层 ├── service // 业务层接口 ├── service.impl // 业务层实现 ├── dao // 数据访问层接口 (Mybatis Mapper) ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象用于层间数据传递 └── config // 配置类如Mybatis配置、拦截器等pom.xml文件是项目的依赖清单。除了 SpringBoot 的 starter 依赖外关键依赖包括spring-boot-starter-web: 提供Web开发能力。mybatis-spring-boot-starter: Mybatis与SpringBoot的集成。mysql-connector-java: MySQL数据库驱动。spring-boot-starter-thymeleaf: Thymeleaf模板引擎支持。druid-spring-boot-starter: 我选择了阿里云的Druid作为数据库连接池因为它提供了强大的监控和防SQL注入能力。lombok: 用于简化实体类的Getter/Setter等代码编写。这里有个小技巧在整合 Mybatis 时务必在application.yml或application.properties中配置mapper-locations指向你的 XML 映射文件路径否则 Mybatis 会找不到 SQL 语句。我通常配置为classpath:mapper/*.xml。3. 核心功能模块实现详解接下来我们深入到几个核心功能模块看看代码具体是如何组织的。我会挑几个有代表性的场景不仅展示“怎么做”更重点解释“为什么这么做”。3.1 用户认证与会话管理商城系统离不开用户体系。我实现了基于 Session 的传统认证方式虽然现在 JWT 更流行于前后端分离架构但对于 Thymeleaf 这种服务端渲染的项目Session 管理起来更直接。实体类与数据库表 首先User实体类定义了用户的基本信息如用户名、密码存储密文、邮箱、手机号等。这里我使用了Data注解Lombok来避免手写 getter/setter。Service层逻辑 在UserServiceImpl中用户注册的逻辑不仅仅是插入数据库。它包含了检查用户名是否已存在防止重复注册。对用户输入的明文密码进行加密。我使用的是 Spring Security 的BCryptPasswordEncoder它是目前存储密码的推荐方式能有效抵御彩虹表攻击。将加密后的密码连同其他信息存入数据库。用户登录时Service 会根据用户名查询用户。使用BCryptPasswordEncoder.matches()方法比对用户输入的密码和数据库存储的密文。如果匹配成功则将用户对象存入 HttpSession 中。关键代码片段如下// 在LoginController中 PostMapping(/login) public String login(RequestParam String username, RequestParam String password, HttpSession session) { User user userService.login(username, password); if (user ! null) { session.setAttribute(loginUser, user); // 用户信息存入Session return redirect:/index; // 登录成功跳转首页 } else { // ... 处理登录失败返回错误信息到页面 } }Thymeleaf页面中的状态判断 在首页或其他需要显示用户登录状态的页面我们可以利用 Thymeleaf 的表达式来动态显示内容!-- 在Thymeleaf模板中 -- div th:if${session.loginUser ! null} 欢迎span th:text${session.loginUser.username}用户/span a href/logout退出/a /div div th:unless${session.loginUser ! null} a href/login登录/a | a href/register注册/a /div这种方式简单有效但需要注意 Session 的超时管理可以在application.yml中配置server.servlet.session.timeout。3.2 图书展示与分页查询图书列表是商城的门面。这里涉及到两个关键技术点Mybatis 动态 SQL 实现多条件查询以及物理分页。Mybatis Mapper 与动态 SQL 在BookMapper.xml中我定义了一个用于条件查询的语句。假设前端可以通过书名、作者、分类进行筛选select idselectByCondition resultTypecom.example.bookstore.entity.Book SELECT * FROM book where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testauthor ! null and author ! AND author LIKE CONCAT(%, #{author}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if AND status 1 !-- 只查询上架图书 -- /where ORDER BY create_time DESC /select这里使用了where和if标签来动态拼接 SQL 的 WHERE 子句避免了因为某个条件为空而导致 SQL 语法错误。#{}是 Mybatis 的参数占位符它能有效防止 SQL 注入。Service层实现分页 我使用了 Mybatis 的插件PageHelper来实现物理分页它非常方便。首先在pom.xml中引入依赖然后在 Service 方法中调用Override public PageInfoBook getBooksByPage(int pageNum, int pageSize, String name, String author, Integer categoryId) { // 紧跟在查询方法前的startPage调用会自动对下一个Mybatis查询进行分页 PageHelper.startPage(pageNum, pageSize); ListBook bookList bookMapper.selectByCondition(name, author, categoryId); // 用PageInfo包装结果它包含了分页的所有信息总记录数、总页数、当前页数据等 return new PageInfo(bookList); }PageInfo对象会被传递给 Thymeleaf 模板。在模板中我们可以这样渲染分页导航!-- 遍历当前页的图书列表 -- div th:eachbook : ${pageInfo.list} !-- 显示图书信息 -- /div !-- 分页条 -- div 共 span th:text${pageInfo.total}/span 条记录 当前第 span th:text${pageInfo.pageNum}/span 页 / 共 span th:text${pageInfo.pages}/span 页 a th:href{/book/list(pageNum1)}首页/a a th:if${pageInfo.hasPreviousPage} th:href{/book/list(pageNum${pageInfo.prePage})}上一页/a a th:if${pageInfo.hasNextPage} th:href{/book/list(pageNum${pageInfo.nextPage})}下一页/a a th:href{/book/list(pageNum${pageInfo.pages})}末页/a /div注意PageHelper.startPage(pageNum, pageSize)必须紧跟在需要分页的查询方法之前调用且只对其后的第一个Mybatis 查询语句生效。如果中间有其他查询会导致分页混乱这是一个非常容易踩的坑。3.3 购物车与订单的创建流程购物车和订单是电商系统的核心涉及事务和业务逻辑的完整性。购物车实现 购物车数据我选择存储在 Session 中因为它是用户临时的、私有的数据。我定义了一个Cart类内部包含一个MapInteger, CartItem键是图书ID值是购物车项包含图书信息、购买数量、小计金额。添加商品检查 Session 中是否有 Cart 对象没有则创建。然后检查 Map 中是否已有该图书有则数量加一无则新建 CartItem 放入 Map。更新数量/删除直接操作 Map 即可。计算总价遍历 Map 中所有 CartItem 的小计并求和。这种实现简单快捷但缺点是无法持久化用户关闭浏览器或 Session 过期后购物车就清空了。对于要求更高的场景可以结合数据库存储。订单创建与事务管理 下单是典型的需要事务管理的操作。步骤包括从 Session 中获取购物车和当前登录用户。计算订单总金额。开启数据库事务。向order表插入一条主订单记录。遍历购物车向order_item表插入多条订单明细记录。扣减库存这是一个关键且易出错的点。需要根据order_item中的图书ID和数量去book表中执行stock stock - quantity的更新操作。这里必须考虑并发超卖问题。清空 Session 中的购物车。提交事务。如果任何一步失败则回滚整个事务。在 Spring 中我们使用Transactional注解来声明事务。关键代码如下Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private BookMapper bookMapper; Override Transactional(rollbackFor Exception.class) // 发生任何异常都回滚 public String createOrder(User user, Cart cart) { // 1. 生成订单号如使用时间戳随机数 String orderNo generateOrderNo(); // 2. 创建主订单对象并插入 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(user.getId()); order.setTotalAmount(cart.getTotalPrice()); // ... 设置其他字段 orderMapper.insert(order); // 3. 创建订单明细并扣减库存注意并发 for (CartItem item : cart.getItems().values()) { OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setBookId(item.getBook().getId()); orderItem.setQuantity(item.getQuantity()); orderItem.setPrice(item.getBook().getPrice()); orderItemMapper.insert(orderItem); // 扣减库存 - 使用乐观锁或悲观锁防止超卖 int affectedRows bookMapper.reduceStock(item.getBook().getId(), item.getQuantity()); if (affectedRows 0) { // 库存不足或更新失败抛出异常触发事务回滚 throw new RuntimeException(商品[ item.getBook().getName() ]库存不足下单失败); } } return orderNo; } }核心踩坑点库存超卖。上面的reduceStock方法对应的 SQL 应该是UPDATE book SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}。这个WHERE stock #{quantity}条件是防止超卖的关键。它保证了只有在库存充足时才会执行扣减并且affectedRows返回被更新的行数应为1。如果返回0说明库存不足我们抛出异常整个事务回滚订单创建失败库存也不会被错误扣减。这是处理高并发下单场景的基础手段更复杂的场景可能需要引入分布式锁或消息队列。4. 开发中的关键配置与性能优化项目能跑起来只是第一步让它跑得稳、跑得快还需要一些关键的配置和优化技巧。4.1 Mybatis 的精细化配置与 SQL 优化打印 SQL 日志在开发阶段查看 Mybatis 实际执行的 SQL 至关重要。在application.yml中配置logging: level: com.example.bookstore.dao: DEBUG # 将你的Mapper接口所在包的日志级别设为DEBUG这样可以在控制台看到完整的 SQL 语句和参数方便调试。但生产环境记得关闭以免日志泛滥。#{} 与 ${} 的区别与选择这是 Mybatis 面试必考题。简单来说#{}是预编译处理PreparedStatementMybatis 会将其替换为?然后由 PreparedStatement 的 set 方法安全地赋值。能有效防止 SQL 注入绝大多数情况都应该使用它。${}是字符串替换。Mybatis 会直接将其替换为变量的值不做任何处理。存在 SQL 注入风险。仅用于需要动态传入表名、列名等非值参数的地方例如分表场景SELECT * FROM ${tableName}。使用二级缓存对于查询远多于修改的数据如图书分类可以开启 Mybatis 的二级缓存。在对应的Mapper.xml文件中添加cache/标签。但要注意分布式环境下默认的基于本地的二级缓存会导致数据不一致需要集成 Redis 等集中式缓存。4.2 Thymeleaf 模板的实用技巧与布局模板布局 (Layout Dialect)为了避免每个页面重复编写头部导航和底部版权我使用了 Thymeleaf 的布局方言。首先在pom.xml中引入thymeleaf-layout-dialect依赖。然后创建一个layout.html作为基础模板!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org xmlns:layouthttp://www.ultraq.net.nz/thymeleaf/layout head title layout:title-pattern$CONTENT_TITLE - 购书商城默认标题/title !-- 公共CSS/JS -- /head body div th:replacefragments/header :: header头部导航/div div layout:fragmentcontent !-- 这里是页面具体内容将被替换的位置 -- /div div th:replacefragments/footer :: footer底部版权/div /body /html具体的页面如index.html就可以这样写!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org xmlns:layouthttp://www.ultraq.net.nz/thymeleaf/layout layout:decorate~{layout} !-- 指定使用哪个布局文件 -- head title首页/title /head body div layout:fragmentcontent h1欢迎来到购书商城/h1 !-- 首页特有的内容 -- /div /body /html这样页面的公共部分和独有部分就完美解耦了。表单处理与数据回显在编辑用户信息或提交搜索表单时Thymeleaf 能很好地与 Spring MVC 的模型绑定配合。例如一个搜索表单form th:action{/book/list} methodget input typetext namename th:value${param.name} placeholder书名/ input typetext nameauthor th:value${param.author} placeholder作者/ button typesubmit搜索/button /formth:value${param.name}可以在提交后将上次搜索的关键词回显到输入框中提升用户体验。4.3 数据库连接池与基础性能考量我选择了 Druid 连接池并在application.yml中进行了详细配置spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 # 监控统计相关配置 web-stat-filter: enabled: true stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: admininitial-size、min-idle、max-active需要根据实际并发量调整。初始值不宜过大避免启动时占用过多资源。开启stat-view-servlet后访问http://你的域名:端口/druid可以进入Druid的监控后台查看SQL执行情况、慢查询等是性能调优的利器。MySQL 层面的优化为常用查询字段建立索引例如order表的user_id、create_timebook表的category_id、name前缀索引。避免SELECT *在 Mybatis 的 Mapper XML 中明确写出需要查询的字段列表减少网络传输和内存占用。关注慢查询日志在 MySQL 配置中开启慢查询日志定期分析对执行时间过长的 SQL 进行优化。5. 项目部署与上线注意事项开发完成最终要部署到服务器上。从本地环境到生产环境有几个地方需要特别注意。5.1 配置文件的多环境隔离绝对不要把数据库密码等敏感信息硬编码在代码里。SpringBoot 支持多环境配置。我通常会创建以下文件application.yml主配置文件放一些通用配置。application-dev.yml开发环境配置连接本地数据库。application-prod.yml生产环境配置连接线上数据库密码等从环境变量或配置中心读取。在application.yml中通过spring.profiles.active指定激活哪个环境。生产环境部署时可以通过启动命令参数--spring.profiles.activeprod来激活生产配置。生产环境数据库密码建议通过环境变量注入# application-prod.yml spring: datasource: url: jdbc:mysql://prod-db-host:3306/bookstore?... username: prod_user password: ${DB_PASSWORD:} # 从环境变量DB_PASSWORD读取如果为空则用冒号后的默认值这里为空然后在服务器上设置DB_PASSWORD环境变量。5.2 打包与部署到 Linux 服务器使用 Maven 打包mvn clean package -DskipTests会在target目录下生成一个可执行的jar文件例如bookstore-0.0.1-SNAPSHOT.jar。部署到 Linux 服务器如 CentOS 或 Ubuntu的典型步骤通过scp或 FTP 将jar包上传到服务器。在服务器上运行java -jar bookstore-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod。但这会在前台运行终端关闭程序就停了。所以我们需要使用后台运行的方式并管理进程。我常用systemd来管理 SpringBoot 应用。创建一个服务单元文件/etc/systemd/system/bookstore.service[Unit] DescriptionBookstore Application Aftersyslog.target network.target [Service] Typesimple Userappuser # 建议用一个非root用户运行 ExecStart/usr/bin/java -jar /path/to/your/bookstore-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod SuccessExitStatus143 Restartalways # 崩溃后自动重启 [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl enable bookstore.service # 开机自启 sudo systemctl start bookstore.service # 启动服务 sudo systemctl status bookstore.service # 查看状态这样你的应用就作为一个系统服务在后台稳定运行了。5.3 上线前的安全检查清单关闭 Swagger 和 Druid 监控的默认路径如果引入了 Swagger 或开启了 Druid 监控生产环境一定要修改其访问路径并设置强密码甚至通过防火墙/IP白名单限制访问。检查依赖版本确保所有第三方依赖特别是数据库驱动、连接池使用的是稳定版本而不是快照版SNAPSHOT。处理静态资源Thymeleaf 模板引用的 CSS、JS、图片等静态资源确保在生产环境下路径正确。SpringBoot 默认从classpath:/static/等目录提供静态资源。日志配置配置合理的日志级别生产环境一般用 INFO 或 WARN并设置日志滚动策略避免单个日志文件过大。压力测试使用 JMeter 或 Apache Bench 对核心接口如首页加载、下单接口进行简单的压力测试确保在预期并发下不会快速崩溃。回顾整个项目从技术选型到功能实现再到部署上线每一个环节都充满了选择和权衡。这套 SpringBoot Mybatis Thymeleaf MySQL 的组合对于构建一个功能完备、结构清晰的单体应用来说依然是性价比极高的选择。它让你能接触到从数据库设计、后端业务逻辑、到前端页面渲染的完整链路是夯实 Java Web 开发基础的绝佳实践。在实际编码中多思考数据流动的边界、事务的边界、异常的处理这些才是比单纯实现功能更宝贵的经验。本文还有配套的精品资源点击获取
返回列表