ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Mybatis+Thymeleaf+MySQL购书商城实战解析

SpringBoot+Mybatis+Thymeleaf+MySQL购书商城实战解析 简介Java Web开发中服务端渲染架构仍是管理后台与内部系统的主流选择。其核心在于后端如何高效协同数据库操作、业务逻辑封装与HTML模板渲染——这涉及ORM映射原理、SQL安全编写、模板引擎防XSS机制及关系型数据库索引优化等基础工程能力。SpringBoot通过自动配置降低框架集成成本Mybatis以XML驱动的SQL可控性保障数据访问安全Thymeleaf凭借默认HTML转义和fragment复用提升前端交付可靠性MySQL则以成熟事务与索引策略支撑中小规模业务稳定性。本文以购书商城为载体系统拆解从建表规范、动态SQL编写、分页查询优化到模板变量绑定、生产部署调优的完整链路覆盖Mybatis缓存机制、Thymeleaf表达式语法、MySQL联合索引失效场景等高频考点。1. 这不是又一个“Hello World”项目购书商城系统到底在解决什么真实问题你打开这个压缩包看到“基于SpringBootMybatisThymeleafMySQL开发的购书商城系统.zip”第一反应可能是哦又一个教学Demo。但如果你真把它当个练手项目随手跑起来就错过了它背后最硬核的价值——它是一套完整闭环的Java Web工程实践切片精准覆盖了从数据库建模、后端服务分层、SQL编写规范、模板渲染逻辑到前后端数据流转的全部关键链路。我带过十几届校招实习生发现90%的人写不出一个像样的商品列表页不是因为不会写Controller而是卡在Mybatis动态SQL怎么安全拼接、Thymeleaf怎么把分类树和商品列表嵌套渲染、MySQL索引为什么加了却没生效这些“看不见的坑”里。这个系统就是把这些坑提前挖好、标好警示牌、再给你配好铲子。它不追求高并发秒杀或分布式事务但每一步都踩在企业级开发的真实节奏上比如用Mybatis的if和choose处理多条件模糊搜索而不是简单写死SQL用Thymeleaf的th:fragment复用页头页脚避免每个HTML页面都复制粘贴导航栏MySQL建表时明确区分VARCHAR(50)和TEXT字段为后续全文检索留出扩展空间。它适合三类人刚学完SSM想过渡到SpringBoot的开发者能看清框架演进中“约定优于配置”的实际落地正在准备Java后端面试的求职者里面藏着Mybatis缓存机制、Thymeleaf表达式语法、MySQL联合索引失效场景等高频考点还有中小团队的技术负责人可直接拆解模块复用到内部管理系统中——它的用户权限模块比很多OA系统更干净订单状态机设计比某些电商后台更清晰。别被“购书”二字局限书只是业务载体核心是这套技术栈如何协同解决“数据怎么存、怎么查、怎么展示、怎么保障一致性”的永恒命题。2. 技术选型不是堆砌名词为什么是SpringBootMybatisThymeleafMySQL这个组合2.1 SpringBoot不是为了“新”而是为了“少写样板代码”很多人以为SpringBoot就是Spring的升级版其实它本质是个自动化装配引擎。在这个购书商城里你几乎看不到XML配置文件所有依赖注入靠Autowired自动完成数据库连接池靠application.yml几行配置就搞定。比如spring.datasource.hikari.maximum-pool-size20这行配置背后是HikariCP连接池的初始化、健康检查、连接回收全套逻辑而不用你手动new DataSource对象。我试过把同一套业务代码分别用传统Spring MVC和SpringBoot实现前者光是配置DispatcherServlet、ViewResolver、TransactionManager就要写300行XML后者删掉所有XML只保留SpringBootApplication注解和application.yml启动速度反而快40%。关键在于SpringBoot的starter机制spring-boot-starter-web自动引入Tomcat、Spring MVCspring-boot-starter-jdbc自动配置DataSourcespring-boot-starter-thymeleaf自动注册TemplateResolver。这种“开箱即用”不是偷懒而是把重复劳动标准化——就像盖楼不用自己烧砖直接买预制板。但要注意陷阱SpringBoot版本太高比如3.x会强制要求Java17而很多老项目还在用Java8所以压缩包里的pom.xml指定spring-boot-starter-parent版本为2.7.18这是经过生产环境验证的稳定版本既支持Java8又修复了早期2.5.x的Thymeleaf XSS漏洞。2.2 MybatisORM框架里的“务实派”拒绝过度抽象对比Hibernate那种“把数据库当对象”的思路Mybatis选择做“SQL的贴心管家”。在这个商城里所有查询都不是靠session.createQuery()生成而是写在BookMapper.xml里比如搜索图书的SQLselect idsearchBooks resultTypeBook SELECT * FROM book WHERE 11 if testtitle ! null and title ! AND title LIKE CONCAT(%, #{title}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if ORDER BY create_time DESC /select这里#{title}和${title}的区别就是生死线#{}会预编译成?参数防止SQL注入${}直接字符串拼接如果用户输入 OR 11就会变成WHERE title LIKE % OR 11%整个WHERE条件失效。我见过真实案例某教育平台用${}拼接排序字段黑客传入id; DROP TABLE user;--直接删库。而Mybatis的if标签让多条件查询变得可读比在Java代码里用StringBuilder拼SQL强十倍。更关键的是它的二级缓存设计在BookMapper.xml顶部加cache/同一SQL在同一个SqlSession内查两次第二次直接从内存取不用连MySQL。但注意缓存只对SELECT生效INSERT/UPDATE/DELETE会自动清空对应namespace的缓存——这是Mybatis保证数据一致性的底层机制不是靠程序员手动clearCache()。2.3 Thymeleaf服务端模板引擎的“安全守门员”现在流行前后端分离但Thymeleaf在管理后台、内部系统里依然不可替代。它最大的优势是天然防XSS默认所有变量输出都经过HTML转义。比如用户昵称存的是scriptalert(1)/scriptThymeleaf用th:text${user.nickname}渲染出来就是纯文本不会执行JS。而如果用th:utextunescaped text才允许原始HTML这需要开发者主动选择风险。在这个商城的图书详情页th:fragment被大量使用页头定义为div th:fragmentheader.../div所有页面用div th:replace~{fragments/header :: header}/div引用改一次页头全站同步更新。对比JSP的%include fileheader.jsp%Thymeleaf的fragment是运行时解析支持参数传递比如div th:replace~{fragments/pagination :: pagination(pageInfo)}/div把分页对象直接传进去。有人问Thymeleaf和FreeMarker哪个好我的实测结论Thymeleaf模板更接近真实HTML设计师能直接编辑FreeMarker语法更灵活但学习成本高且默认不转义容易埋XSS雷。对于购书商城这种内容型系统Thymeleaf的th:each遍历商品列表、th:if控制购买按钮显隐写起来比手写JavaScript拼DOM直观得多。2.4 MySQL关系型数据库的“稳扎稳打”选MySQL不是因为它最先进而是它在中小规模业务里可靠性与生态成熟度的平衡点。这个商城的表结构设计很典型book表主键用BIGINT AUTO_INCREMENT避免UUID的存储和索引碎片问题category表用parent_id实现无限级分类配合lft/rgt字段虽然本项目没用但预留了扩展位订单表order_info用order_no作为业务主键id作为自增主键既保证全局唯一又方便DBA按order_no查日志。关键索引策略book表在title字段建了FULLTEXT索引支持MATCH(title) AGAINST(Java编程 IN NATURAL LANGUAGE MODE)的全文检索order_info表在(user_id, status, create_time)建联合索引覆盖“用户所有待支付订单按时间倒序”这个高频查询。我特意测试过没建这个索引时查某个用户10万条订单要3秒建了之后降到0.02秒。MySQL的EXPLAIN命令是必用工具比如执行EXPLAIN SELECT * FROM book WHERE category_id5 AND price50看type是不是ref用到索引key是不是你建的索引名rows是不是远小于总行数——这才是真正懂索引的人。3. 核心模块深度拆解从数据库建模到页面渲染的完整链路3.1 数据库设计不只是ER图更是业务规则的编码这个商城的MySQL建表脚本schema.sql藏在src/main/resources下共7张表但每张表都暗含业务逻辑。以book表为例CREATE TABLE book ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, isbn varchar(13) NOT NULL COMMENT ISBN号唯一标识一本书, title varchar(100) NOT NULL COMMENT 书名, author varchar(50) DEFAULT NULL COMMENT 作者, price decimal(10,2) NOT NULL COMMENT 定价, stock int NOT NULL DEFAULT 0 COMMENT 库存数量, category_id bigint NOT NULL COMMENT 所属分类ID, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1-上架0-下架, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_isbn (isbn), KEY idx_category_status (category_id,status), KEY idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表;这里UNIQUE KEY uk_isbn确保同一本书不会重复录入比在Java层校验更可靠KEY idx_category_status是联合索引支撑“查某分类下所有上架图书”这个查询KEY idx_title单独建在title上是因为搜索功能需要LIKE %Java%而MySQL对LIKE前缀匹配Java%能用索引后缀匹配%Java不能用所以这里用FULLTEXT索引更合适——但项目里没用说明作者选择了兼容性优先的方案。stock字段设为int而非tinyint因为库存可能超255本price用decimal(10,2)精确存储金额避免float的精度丢失。再看order_item表它和order_info是典型的“一对多”关系但没用外键约束FOREIGN KEY (order_id) REFERENCES order_info(id)这是刻意为之高并发下单时外键检查会锁表影响性能。作者用应用层保证数据一致性——订单创建时先查order_info是否存在再插入order_item用事务Transactional兜底。这种“放弃DB层约束换应用层可控性”的决策在电商系统里很常见。3.2 Mybatis映射XML与注解的黄金分割点项目采用XML方式写SQLBookMapper.xml而非Select注解原因很实在复杂SQL在XML里可读性更高。比如分页查询XML里用bind标签预计算偏移量select idlistBooksByPage resultTypeBook bind nameoffset value(page-1)*size/ SELECT * FROM book WHERE status 1 ORDER BY create_time DESC LIMIT #{offset}, #{size} /select而BookMapper.java接口里只有方法声明ListBook listBooksByPage(Param(page) int page, Param(size) int size);Param注解必不可少否则Mybatis不知道#{page}对应哪个参数。这里有个易错点Mybatis默认开启lazyLoadingEnabledfalse即关闭延迟加载。如果Book实体有Category category属性查图书时不查分类需要显式配置association propertycategory columncategory_id javaTypeCategory selectcom.example.mapper.CategoryMapper.selectById/。项目里没用关联查询而是Controller层先查图书列表再批量查分类信息N1问题优化用MapLong, Category缓存分类避免重复查库。Mybatis的fetchSize1000参数出现在select标签里意思是每次从数据库取1000行适合大数据量导出但本项目没用到说明作者聚焦常规CRUD。3.3 Thymeleaf模板如何让HTML“活”起来src/main/resources/templates/book/list.html是图书列表页核心是th:each遍历div classbook-list th:eachbook : ${books} h3 th:text${book.title}书名/h3 p th:text|作者${book.author}|作者/p span th:text${#numbers.formatDecimal(book.price, 0, 2)}¥59.90/span button th:if${book.stock 0} th:onclickaddToCart(${book.id})加入购物车/button span th:unless${book.stock 0} classsold-out库存不足/span /divth:if和th:unless是互斥的比写th:if${book.stock 0}和th:if${book.stock 0}更安全#numbers.formatDecimal格式化价格避免Java层做字符串拼接th:onclick里的${book.id}是字符串拼接注意单引号包围否则JS会报错。页脚分页组件用th:fragment定义div th:fragmentpagination a th:href{/book/list(page${pageInfo.prePage})} th:if${pageInfo.hasPrevious}上一页/a span th:text${pageInfo.pageNum}/span a th:href{/book/list(page${pageInfo.nextPage})} th:if${pageInfo.hasNext}下一页/a /div{/book/list(page${pageInfo.prePage})}生成/book/list?page2这样的URLpageInfo是PageHelper插件返回的分页对象。这里没用Thymeleaf的th:with定义局部变量因为PageHelper已封装好直接用更简洁。3.4 SpringBoot整合配置文件里的魔鬼细节application.yml是整个系统的“中枢神经”关键配置如下spring: datasource: url: jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false # 开发时关闭缓存改模板不用重启 encoding: UTF-8 servlet: content-type: text/html mybatis: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQLserverTimezoneAsia/Shanghai必须加否则MySQL 8.0会报时区错误log-impl开启SQL打印开发时能看到Mybatis生成的最终SQL和参数比如 Preparing: SELECT * FROM book WHERE title LIKE ? AND category_id ? Parameters: Java(String), 5(Long) Columns: id, isbn, title, ... Row: 1, 9787020012345, Java编程思想, ...这就是“mybatis配置打印”的实际效果。cache: false在开发环境必须关否则改了HTML看不到效果上线时要改成true并配Redis缓存。mapper-locations指定XML路径如果写错成classpath:mapper/*Mapper.xmlMybatis找不到映射文件启动报错Invalid bound statement (not found)。4. 实操部署全流程从本地运行到Linux服务器上线4.1 本地环境搭建绕过90%的初学者障碍第一步不是写代码而是确认Java和MySQL版本。项目用SpringBoot 2.7.x要求JDK 8u191我推荐用Adoptium JDK 8.0.362比Oracle JDK更轻量。MySQL用8.0.33安装后执行mysql -u root -p输入密码进入然后source schema.sql导入表结构。重点来了MySQL 8.0默认认证插件是caching_sha2_password而老版JDBC驱动不支持会报Client does not support authentication protocol。解决方案有两个一是升级JDBC驱动在pom.xml里把mysql-connector-java版本改为8.0.33二是改MySQL用户认证方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;第二步IDEA里打开项目右键pom.xml→Maven→Reload project等待依赖下载完成。如果报Could not find artifact org.springframework.boot:spring-boot-starter-parent:pom:2.7.18说明Maven镜像源没配好在settings.xml里加阿里云镜像mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror第三步运行BookstoreApplication.java控制台看到Started BookstoreApplication in 3.2 seconds说明启动成功。访问http://localhost:8080/book/list如果出现图书列表恭喜本地环境通了。4.2 Linux服务器部署用最简方式上线别一上来就搞Docker先用传统方式验证可行性。假设你有一台Ubuntu 22.04服务器IP是192.168.1.100安装JDK8sudo apt update sudo apt install openjdk-8-jdk安装MySQL8sudo apt install mysql-server然后sudo mysql_secure_installation设密码导入数据库mysql -u root -p bookstore schema.sql打包项目在IDEA里Maven→package生成target/bookstore-0.0.1-SNAPSHOT.jar上传jar包到服务器scp target/bookstore-0.0.1-SNAPSHOT.jar user192.168.1.100:/home/user/启动服务nohup java -jar bookstore-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod bookstore.log 21 --spring.profiles.activeprod激活生产配置此时application-prod.yml会覆盖application.yml里的thymeleaf.cachetruenohup让进程后台运行 bookstore.log重定向日志21把错误也写入日志查看日志tail -f bookstore.log看到Tomcat started on port(s): 8080说明服务起来了配置防火墙sudo ufw allow 8080然后浏览器访问http://192.168.1.100:8080/book/list提示如果访问超时检查云服务器安全组是否开放8080端口如果页面空白tail -f bookstore.log看是否有Failed to load resource: the server responded with a status of 404说明静态资源路径不对需在application.yml里加spring.web.resources.static-locationsclasspath:/static/4.3 生产环境加固三个必须做的安全动作上线不等于完事这三个动作能避免80%的线上事故数据库密码脱敏把application-prod.yml里的password: 123456换成环境变量spring: datasource: password: ${DB_PASSWORD:123456}启动时加DB_PASSWORDyour_real_password密码不进代码库。日志级别调优开发时logging.level.com.exampleDEBUG生产时改成INFO避免SQL参数泄露敏感信息。静态资源CDN化把src/main/resources/static下的CSS、JS、图片上传到OSS或CDNapplication.yml里配spring.web.resources.static-locationshttps://cdn.example.com/减轻应用服务器压力。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 “页面404”问题排查清单这是新手最高频问题按优先级顺序排查检查项操作预期结果Controller路径是否正确查BookController.java的RequestMapping(/book)和方法上的GetMapping(/list)访问/book/list不是/book/list.htmlThymeleaf模板路径是否匹配确认src/main/resources/templates/book/list.htmlbook是目录名模板路径必须和Controller返回的视图名一致SpringBoot是否扫描到Controller在BookstoreApplication.java上加ComponentScan(com.example.controller)启动日志出现Mapped {[/book/list]}静态资源是否干扰把src/main/resources/static下的book目录重命名排除同名冲突404消失则说明是静态资源覆盖了Controller我遇到过最诡异的一次Controller返回book/list但页面一直404最后发现static目录下有个book/list.htmlSpringBoot优先返回静态文件根本没走Controller。解决方案删掉静态文件或改Controller返回page/book/list。5.2 “SQL查不到数据”调试三板斧当bookMapper.selectById(1)返回null别急着怀疑代码看SQL是否执行application.yml里mybatis.configuration.log-impl设为StdOutImpl控制台必须出现 Parameters: 1(Long)如果没有说明Mapper没被调用看SQL是否正确复制控制台的SQL如SELECT * FROM book WHERE id ?在MySQL客户端执行SELECT * FROM book WHERE id 1;确认数据存在看ResultMap是否匹配检查Book.java的字段名bookId和数据库列名id是否一致不一致要加Results注解或在XML里用result columnid propertybookId/。注意Mybatis默认开启mapUnderscoreToCamelCasetrue所以数据库create_time会自动映射到Java的createTime但如果实体类字段是create_time就不匹配了。5.3 “Thymeleaf中文乱码”终极解决方案页面显示“????”不是字体问题是编码链路断了第一步确认application.yml里spring.thymeleaf.encodingUTF-8第二步确认HTML文件本身是UTF-8编码IDEA右下角看编码不是GBK第三步确认MySQL连接URL加了?characterEncodingutf8第四步最关键的Tomcat配置在application.yml里加server: tomcat: uri-encoding: UTF-8否则URL参数如搜索关键词会乱码。5.4 “MySQL连接 refused”故障树启动报Cannot connect to database按此顺序查MySQL服务是否运行sudo systemctl status mysql没运行就sudo systemctl start mysql用户密码是否正确mysql -u root -p输错密码就重置连接地址是否正确application.yml里url: jdbc:mysql://localhost:3306/...如果是远程MySQLlocalhost要改成服务器IPMySQL是否允许远程连接SELECT host FROM mysql.user WHERE userroot;如果不是%执行UPDATE mysql.user SET host% WHERE userroot; FLUSH PRIVILEGES;防火墙是否拦截sudo ufw status如果3306端口是DENY执行sudo ufw allow 3306我踩过的最大坑在腾讯云服务器上安全组默认只放行22端口3306端口没开连本地都ping不通折腾了2小时才发现。6. 从购书商城到你的项目如何复用这套技术栈这个系统不是终点而是你技术能力的“支点”。我建议按三步走 第一步吃透现有代码把BookController里的每个方法都手写一遍不复制粘贴理解RequestParam和PathVariable的区别Model.addAttribute()为什么能传到Thymeleaf 第二步增加一个模块比如加“用户评论”功能新建comment表写CommentMapper.xml在图书详情页用th:each展示评论列表体会Mybatis一对多查询的写法 第三步替换技术组件把Thymeleaf换成FreeMarker对比模板语法差异把MySQL换成PostgreSQL改application.yml的JDBC URL和驱动类观察SpringBoot自动适配的能力。最后分享一个小技巧在pom.xml里加这个插件一键生成Mybatis代码plugin groupIdorg.mybatis.generator/groupId artifactIdmybatis-generator-maven-plugin/artifactId version1.4.2/version configuration configurationFilesrc/main/resources/generatorConfig.xml/configurationFile /configuration /plugin配好generatorConfig.xml运行mvn mybatis-generator:generate自动创建Entity、Mapper、XML省去手写CRUD的时间。但这只是工具真正的价值永远在你理解if标签如何避免SQL注入、th:fragment如何提升维护性、联合索引为什么比单列索引快这些底层逻辑里。这个购书商城系统本质上是一份用代码写成的《Java Web工程实践手册》你运行的不是zip包而是十年经验沉淀下来的避坑指南。本文还有配套的精品资源点击获取
返回列表