ARTICLE DETAIL

资讯详情

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

Java+MySQL校园二手书交易平台:从表结构设计到防超卖实战

Java+MySQL校园二手书交易平台:从表结构设计到防超卖实战 简介面向本科毕业设计的校园二手书交易平台完整资料包涵盖项目源码、高分论文、数据库脚本与说明文档适合软件工程等相关专业学生用于毕业设计参考或二次开发。该平台基于Java和MySQL实现针对图书信息管理效率低的问题完整展示了从可行性分析、系统设计、数据库设计到管理员功能实现与系统测试的全流程。压缩包共809个文件大小25.09MB主要包含java后端源码、vue/js前端代码、css样式、html页面、sql数据库脚本以及doc/pdf格式的论文文档目录结构清晰便于按模块查阅。已有82人学习下载。借助该资料读者可掌握校园二手书交易平台的开发技术要点学习系统需求分析、设计、测试的规范方法同时借鉴作者遇到的问题及解决思路强化实践与理论结合的能力。1. 校园二手书交易平台这个 JavaMySQL 项目到底解决什么问题校园二手书交易平台这类题目在毕业设计里属于“看起来简单、想拿高分并不容易”的典型用户注册登录、图书发布、下单购买、订单管理功能清单一列就是十几项但真把源码、数据库脚本和论文交上去评委最常问的恰恰不是功能做没做出来而是订单状态怎么流转、并发下单会不会超卖、数据库为什么这么设计。这个基于 Java 和 MySQL 的校园二手书交易平台就是围绕这一整套需求来的适合正在做毕设或课设、想从只会 CRUD 往上走一步的人。它能让你在答辩时讲清楚表结构设计、事务边界和并发处理三件事这三件事正好也是 Java 后端面试里被反复问到的点。2. 表结构设计与选型先想清楚再动手写代码2.1 为什么是 Java MySQL这套组合的能力边界常见做法是 Spring Boot MyBatis MySQL标题只写了 Java 和 MySQL但落地时 Spring Boot 几乎是绕不开的底座它解决了项目里最麻烦的配置问题内嵌 Tomcat、自动装配数据源、依赖管理。MySQL 这边网上 mysql 安装教程和 mysql 安装配置教程非常成熟5.7 和 8.0 都有大量现成资料遇到问题时搜索成本低。这套组合的能力边界也很明确单机部署、小并发、十万级数据量内毫无压力适合校园场景里一个学校几千人使用的规模。它不适合做分布式、高可用但你把它写在论文的“系统局限性”里反而是加分项。选型时有一个容易被忽视的点Java 的 JDBC 驱动对 MySQL 8.0 和 5.7 的处理不一样。8.0 的驱动类名是com.mysql.cj.jdbc.Driver5.7 用的是com.mysql.jdbc.Driver如果你把网上杂七杂八的配置直接复制过来启动大概率报ClassNotFoundException。我在后面避坑章节会专门说这件事。这里先记住结论新项目一律用 MySQL 8.0 com.mysql.cj.jdbc.Driver别为了“兼容旧教程”退回 5.7。2.2 六张核心表设计用户、图书、订单、收藏、分类先把表结构定下来再写代码。很多初学者喜欢先把 Controller 写出来回头发现数据库字段不够用又回去改表来回折腾。我一般会先花一个晚上把表设计定稿后面的编码会顺畅得多。这个平台最少需要六张表用户表、图书表、订单表、订单明细表可选、收藏表、分类表。下面是核心建表 SQL我按 MySQL 8.0 的语法写-- 用户表一个用户既是买家也可以是卖家 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(32) NOT NULL COMMENT 登录名, password VARCHAR(128) NOT NULL COMMENT 加密后的密码用 BCrypt, nickname VARCHAR(32) DEFAULT NULL COMMENT 昵称, phone VARCHAR(16) DEFAULT NULL COMMENT 联系电话, role TINYINT NOT NULL DEFAULT 1 COMMENT 1买家 2卖家 3管理员, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci COMMENT用户表; -- 图书表二手书的核心信息 CREATE TABLE book ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 图书主键, seller_id BIGINT NOT NULL COMMENT 发布者关联 user.id, category_id BIGINT NOT NULL COMMENT 分类关联 category.id, title VARCHAR(64) NOT NULL COMMENT 书名, author VARCHAR(32) DEFAULT NULL COMMENT 作者, publisher VARCHAR(64) DEFAULT NULL COMMENT 出版社, isbn VARCHAR(32) DEFAULT NULL COMMENT ISBN 编号, original_price DECIMAL(10,2) NOT NULL COMMENT 原价, price DECIMAL(10,2) NOT NULL COMMENT 二手售价, description TEXT COMMENT 书况描述, cover_url VARCHAR(255) DEFAULT NULL COMMENT 封面图片地址, stock INT NOT NULL DEFAULT 1 COMMENT 库存默认1本, status TINYINT NOT NULL DEFAULT 1 COMMENT 1在售 2已下单 3已完成 4下架, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号防超卖用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 发布时间, PRIMARY KEY (id), KEY idx_seller (seller_id), KEY idx_status (status), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表;两个细节值得注意。第一金额字段用DECIMAL(10,2)绝不能用FLOAT或DOUBLE浮点数的二进制表示会让金额在计算时出现 0.1 0.2 ≠ 0.3 这种尴尬问题这在答辩现场演示时是致命的。第二status用TINYINT而不是字符串状态码在 Java 侧用枚举维护数据库只存数字这样查询快也不怕字符串拼写错误。订单表和收藏表继续沿用同样的设计风格-- 订单表一次交易一张主订单 CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 订单主键, order_no VARCHAR(32) NOT NULL COMMENT 订单编号业务上唯一, buyer_id BIGINT NOT NULL COMMENT 买家 id, seller_id BIGINT NOT NULL COMMENT 卖家 id, book_id BIGINT NOT NULL COMMENT 图书 id, amount DECIMAL(10,2) NOT NULL COMMENT 成交金额, status TINYINT NOT NULL DEFAULT 1 COMMENT 1待付款 2已付款 3已发货 4已完成 5已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL COMMENT 付款时间, finish_time DATETIME DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 收藏表用户收藏图书模拟“想买但没下单” CREATE TABLE favorite ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 用户 id, book_id BIGINT NOT NULL COMMENT 图书 id, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_book (user_id, book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收藏表;订单表为什么要冗余一个seller_id因为列表页经常要显示“卖家的书”如果只靠book_id去关联查询每次都要 JOIN 两张表冗余字段在订单生成时写入一次就能让“我卖出的订单”和“我买到的订单”各用一条索引查出来这是典型的空间换时间。收藏表用联合唯一索引uk_user_book兜底防止用户对同一本书重复收藏这是数据库层的第一道防重Java 代码里的判断只是第二道。2.3 Controller-Service-Mapper 三层事务应该放在哪一层设计完表接下来是工程结构。Spring Boot 项目里最常见的分包方式是 controller / service / mapper也叫 dao三层实体类放在 entity或 pojo包里。分层的核心原则是Controller 只做参数接收和结果包装不写业务逻辑Service 层承担所有业务规则、事务控制Mapper 层只负责 SQL 交互。很多新手把业务规则写进 Controller结果就是控制器里几百行代码既没法复用也没法加事务这是代码评审时最容易被挑出来的毛病。下面是一个典型的图书发布接口展示三层怎么协作RestController RequestMapping(/api/book) public class BookController { private final BookService bookService; public BookController(BookService bookService) { this.bookService bookService; } PostMapping(/publish) public ResultLong publish(RequestBody BookPublishRequest req) { // Controller 只做一件事把请求参数交给 Service拿到结果返回 Long bookId bookService.publish(req); return Result.success(bookId); } }Service public class BookService { private final BookMapper bookMapper; public BookService(BookMapper bookMapper) { this.bookMapper bookMapper; } Transactional(rollbackFor Exception.class) public Long publish(BookPublishRequest req) { Book book new Book(); book.setSellerId(req.getSellerId()); book.setTitle(req.getTitle()); book.setPrice(req.getPrice()); // 新上架的书籍初始状态是“在售” book.setStatus(BookStatus.ON_SALE.getCode()); book.setVersion(0); bookMapper.insert(book); return book.getId(); } }注意Transactional(rollbackFor Exception.class)这个注解它必须加在 Service 的 public 方法上而不是 Controller 或 Mapper 上。默认情况下 Spring 只对 RuntimeException 回滚加上rollbackFor Exception.class是为了让受检异常比如文件写入失败也能触发回滚。这里有个大坑如果同一个类里的方法 A 调方法 BB 上的Transactional是不生效的因为 Spring 的代理机制只拦截外部调用。后面避坑章节会展开讲。3. 把源码跑起来环境配置到首次联调3.1 JDK、MySQL 与 Maven 的版本搭配拿到项目源码第一件事不是打开 IDE而是检查环境。这里给出一套我用着最稳的搭配JDK 1.8 或 11不要用 17除非项目明确写了支持、MySQL 8.0.x、Maven 3.6.3 或更高版本、Spring Boot 2.7.x。JDK 环境变量配置详细教程网上到处都是核心就两步新建JAVA_HOME指向 JDK 安装目录把%JAVA_HOME%\bin加进Path。装完后在命令行敲java -version确认。MySQL 这边如果你是本机安装用官方安装包一路下一步即可如果你是 Linux 服务器或者想省事用 Docker 装也行但 docker 安装 mysql 失败的案例我见过不少多数是端口占用或者容器内存不足。我个人在本地开发时倾向于直接用 docker-compose 起一个 MySQL 8.0省得卸载时残留一堆服务# docker-compose.yml 片段本地开发足够用 version: 3 services: mysql: image: mysql:8.0 container_name: secondbook-mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: secondbook command: --default-authentication-pluginmysql_native_password volumes: - ./data:/var/lib/mysql这个命令里最容易踩坑的是--default-authentication-pluginmysql_native_password。MySQL 8.0 默认的认证插件是caching_sha2_password而很多老版本的 JDBC 驱动和图形化工具比如老版 Navicat不认这个插件连上去会报Unable to load authentication plugin caching_sha2_password。加上这个参数等于让 MySQL 兼容旧客户端省去后面一堆排查时间。启动后可以用mysql -u root -p验证连接顺手把 mysql 数据库常用命令过一遍show databases;、use secondbook;、show tables;。3.2 建库初始化执行 SQL 脚本的正确姿势项目里一般会带一个database/init.sql里面是建库建表语句和初始数据。新手容易犯的错误是直接用图形化工具把整个 SQL 文件复制粘贴结果表建好了数据库却选错了或者字符集不对。正确姿势是先建库再指定库执行脚本# 先登录建库并指定字符集 mysql -u root -p -e CREATE DATABASE IF NOT EXISTS secondbook DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 再导入脚本注意 -u 和密码之间不要有空格 mysql -u root -p secondbook init.sql最后一行命令的语义是把init.sql文件里的 SQL 语句按顺序执行执行时默认数据库是secondbook。如果你的init.sql里已经写了USE secondbook;那命令里的库名可有可无但写上更保险。执行完要验证一下别急着启动后端USE secondbook; SHOW TABLES; SELECT COUNT(*) FROM user;如果能查到初始化的管理员账号数据说明建库成功。注意字符集务必是utf8mb4别用utf8因为utf8在 MySQL 里其实是utf8mb3存不了 emoji 表情而且utf8mb4是现在的绝对主流这在后面的中文乱码章节还会提到。3.3 application.yml 配置数据源参数逐个说清楚建好库之后打开后端的src/main/resources/application.yml这是整个项目能不能跑起来的关键。网上搜相关项目源码90% 的启动失败都出在这个文件里。下面是完整的配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/secondbook?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 5MB max-request-size: 10MB mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl逐项说明。useSSLfalse代表不启用 SSL 加密连接本地开发可以关掉不然 MySQL 8.0 在有 SSL 配置时会弹告警。serverTimezoneAsia/Shanghai解决时区报错问题这个坑在第 5 章会详细讲。characterEncodingutf8其实写utf8就能正确识别 Unicode某些教程让写utf8mb4在连接串里意义不大因为连接串的编码已经由characterEncoding控制。allowPublicKeyRetrievaltrue是 MySQL 8.0 在客户端第一次连接时获取 RSA 公钥用的不加上去可能报Public Key Retrieval is not allowed。map-underscore-to-camel-case: true这个配置强烈建议打开它能把数据库的seller_id自动映射成 Java 的sellerId省去在 XML 里写一大堆resultMap。log-impl设置成StdOutImpl是为了在控制台直接看到 SQL 日志方便联调排查。刚启动时报错的话先把 MyBatis 日志打出来看这比对着代码猜要快得多。3.4 用 Postman 验证第一个接口注册与登录环境配置完成后启动 Spring Boot 主类看到Tomcat started on port(s): 8080说明项目起来了。第一个要验证的接口通常是注册。这里以注册接口为例# POST http://localhost:8080/api/user/register # Content-Type: application/json { username: test001, password: 123456, nickname: 测书员 }用 Postman 发送之后预期返回{code:200,message:success,data:null}。如果返回的是 500 或连接超时按三个方向排查第一看后端控制台有没有 SQL 异常比如Table secondbook.user doesnt exist说明 SQL 脚本没执行成功第二看连接报错里的地址和端口是不是 3306容器部署的 MySQL 经常因为端口映射没做对导致连不上第三看用户名密码是否和application.yml一致密码错误时 MySQL 返回的报错信息非常明确。注册成功后再测登录接口拿到一个 tokenJWT 或 UUID后把 token 加到请求头Authorization里访问图书列表整个链路就算通了。从“拿到源码”到“调通接口”正常情况下应该控制在一个小时以内超过两小时说明环境哪里没对上不要死磕把报错信息复制下来去搜十有八九是版本匹配问题。4. 交易核心代码实现发布、下单与防超卖4.1 图书发布文件上传与状态字段的配合图书发布是卖家的核心操作。前端表单里有书名、作者、价格、封面图封面图上传是个容易忽略的细节如果一个请求既要传 JSON 又要传文件常见做法是把文件传到一个单独的上传接口返回图片 URL再和表单数据一起提交。后端上传接口一般长这样PostMapping(/api/upload) public ResultString upload(RequestParam(file) MultipartFile file) { // 文件名校验防路径穿越、防重名 String originalName StringUtils.cleanPath(file.getOriginalFilename()); String suffix originalName.substring(originalName.lastIndexOf(.)); String newName UUID.randomUUID().toString().replace(-, ) suffix; // 保存到本地目录生成访问路径 file.transferTo(new File(UPLOAD_DIR newName)); return Result.success(/files/ newName); }这里有两个参数要注意。MultipartFile的文件大小由spring.servlet.multipart.max-file-size控制配的是 5MB超出会抛MaxUploadSizeExceededException接口返回 500体验很差应该在全局异常处理里专门捕获这个异常并返回友好提示。另外文件名绝不能直接用前端传来的原始名第一是有路径穿越风险第二是同名文件会互相覆盖用 UUID 重命名是通用做法。图书发布状态的流转逻辑是新发布的图书状态是1在售用户下单但未付款时如果图书只有一本stock1状态要立刻改成2已下单防止其他用户同时下单同一本书。等订单完成或取消后再回滚状态。这就是状态字段和业务流程的配合论文里画一张状态图评委基本都会点头。4.2 订单状态机从在售到已完成的状态流转订单模块是整个系统的灵魂状态机设计得好不好答辩时三句话就能试探出来。最精简的状态集合是待付款1、已付款2、已完成4、已取消5。可以再加一个已发货3但如果是同校当面交易发货物流环节可以省略别为了凑功能把状态搞复杂。public enum OrderStatus { WAIT_PAY(1, 待付款), PAID(2, 已付款), SHIPPED(3, 已发货), FINISHED(4, 已完成), CANCELED(5, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } }合法的状态迁移只有几条待付款 → 已付款待付款 → 已取消已付款 → 已发货已发货 → 已完成。其余任何跳转都是非法状态。在代码里我一般在 Service 层写一个状态校验私有方法每次更新前先查一次旧状态比对是否允许迁移private void validateStatusChange(Order oldOrder, OrderStatus target) { OrderStatus current OrderStatus.ofCode(oldOrder.getStatus()); boolean valid (current OrderStatus.WAIT_PAY (target OrderStatus.PAID || target OrderStatus.CANCELED)) || (current OrderStatus.PAID target OrderStatus.SHIPPED) || (current OrderStatus.SHIPPED target OrderStatus.FINISHED); if (!valid) { throw new BizException(非法订单状态流转); } }这个方法的本质是把状态机的约束写进代码里而不是散落在各个 if 分支中。新人写订单模块最常见的翻车现场就是买家点取消按钮后端不判断旧状态直接把 state 改成 5。假如一笔订单明明已经付款完成买家还能点击取消那账目就全乱了。用状态机校验哪怕前端故意绕过按钮直接调接口后端也会拒绝。4.3 并发防超卖乐观锁与唯一索引的取舍防超卖是这个项目里最值得写进论文的亮点。场景是这样的一本书定价特别低比如 10 块钱的考研真题一瞬间有 5 个买家同时下单。如果不做并发控制MySQL 的“先查询再更新”会产生丢失更新5 个请求都读到stock1然后都执行update book set stock stock - 1最后库存变成负数但订单却生成了 5 笔。处理这个问题有三种常见方案。第一种是悲观锁select ... for update把行锁住事务提交后才释放。第二种是乐观锁更新时带上版本号判断。第三种是应用层加锁但在分布式环境下不靠谱。在毕设这个规模下首推乐观锁Update(UPDATE book SET stock stock - 1, version version 1 WHERE id #{bookId} AND stock 0 AND version #{oldVersion}) int deductStock(Param(bookId) Long bookId, Param(oldVersion) Integer oldVersion);这条 SQL 的意思清晰只有当前的version和我之前查询到的版本一致时才允许扣减库存库存大于 0 是另一道防线双保险。如果deductStock返回 0说明版本冲突或库存不足Service 层捕获到这个结果直接抛业务异常“这本书已经被抢购了”。注意version字段初始值是 0每次更新加 1这个字段在第 2 章的建表 SQL 里已经预留了。下单的完整流程在 Service 层应该是这样Transactional(rollbackFor Exception.class) public Long createOrder(Long buyerId, Long bookId) { Book book bookMapper.selectById(bookId); // 业务校验书必须在售、不能买自己的书 if (book.getSellerId().equals(buyerId)) { throw new BizException(不能购买自己发布的图书); } if (book.getStatus() ! BookStatus.ON_SALE.getCode()) { throw new BizException(图书已下架或已被购买); } // 扣减库存乐观锁控制并发 int rows bookMapper.deductStock(bookId, book.getVersion()); if (rows 0) { throw new BizException(手慢了这本书刚刚被买走了); } // 生成订单状态为待付款 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setBuyerId(buyerId); order.setSellerId(book.getSellerId()); order.setBookId(bookId); order.setAmount(book.getPrice()); order.setStatus(OrderStatus.WAIT_PAY.getCode()); orderMapper.insert(order); return order.getId(); }整个方法挂在同一个事务里扣库存和插订单要么一起成功要么一起回滚。这就是 mysql 事务处理在业务中的实际落点——你不需要背事务隔离级别但必须知道Transactional能保证跨表的原子性。事务配置上用默认的传播行为REQUIRED就够不要随意改成REQUIRES_NEW否则容易在嵌套调用时产生连接池占用问题。4.4 图书搜索与分页LIKE 与 LIMIT 的正确姿势搜索功能用 MyBatis XML 实现核心是动态 SQL。关键词模糊匹配书名和作者价格区间用范围查询状态只查在售。分页我用最简单的LIMIT ? OFFSET ?不引入 PageHelper减少一个依赖和一层黑匣子select idsearchBooks resultTypecom.example.entity.Book SELECT id, title, author, publisher, price, cover_url, stock, create_time FROM book WHERE status 1 if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR author LIKE CONCAT(%, #{keyword}, %) OR isbn LIKE CONCAT(%, #{keyword}, %)) /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if ORDER BY create_time DESC LIMIT #{pageSize} OFFSET #{offset} /select三个细节。第一LIKE 的模糊匹配不能用%${keyword}%这种${}拼接会有 SQL 注入风险用CONCAT(%, #{keyword}, %)是参数预编译安全。第二和在 XML 里要转义成gt;和lt;不然 XML 解析直接报错。第三分页用OFFSET在数据量大时有性能问题但二手书平台几万条数据的规模完全够用论文里一句“数据量超过十万时考虑游标分页”就能显示你考虑过扩展性。5. 避坑与排查JavaMySQL 项目常见的 6 个翻车现场5.1 时区报错导致启动失败The server time zone value йʱ现象Spring Boot 启动时控制台报The server time zone value йʱ is unrecognized or represents more than one time zone.里面是一串乱码。原因MySQL 安装默认时区是系统时区CST 在英文里指代混乱而 JDBC 驱动需要明确知道服务器时区才能做时间转换配不上就报错。解决在连接串里加上serverTimezoneAsia/Shanghai。如果你的 URL 是jdbc:mysql://localhost:3306/secondbook改成jdbc:mysql://localhost:3306/secondbook?serverTimezoneAsia/ShanghaiuseSSLfalse。这是最直接、最不依赖服务器环境变量的办法。5.2 驱动类名找不到ClassNotFoundException: com.mysql.jdbc.Driver现象报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因项目用的mysql-connector-java是 8.0 版本驱动类名改成了com.mysql.cj.jdbc.Driver旧的com.mysql.jdbc.Driver在 8.0 里变成了过期别名部分版本直接移除。解决把application.yml里的driver-class-name改成com.mysql.cj.jdbc.Driver。同时建议你的 Maven 依赖直接引mysql-connector-j新坐标而不是老旧的mysql-connector-java。5.3 Linux 上建表报错Table xxx doesnt exist现象本地 Windows 跑得好好的部署到 Linux 服务器后代码里操作某张表时报Table secondbook.Book doesnt exist。原因MySQL 在 Linux 上默认区分表名大小写lower_case_table_names0而 Windows 上默认不区分lower_case_table_names1。你建表时写的book代码里如果用了BookWindows 上能查到Linux 上直接说表不存在。解决统一约定表名和字段名全部小写SQL 里也全用对应的小写。或者修改 MySQL 配置设置lower_case_table_names1但注意这个参数必须在初始化时设置才生效改配置文件后重建容器才有效。5.4 前端显示中文乱码??? 或问号现象数据库里查出来是正常中文但页面显示???或者在命令行执行 SQL 插入中文后变成问号。原因连接字符集和表的字符集不一致。表用的utf8mb4但连接串里少了characterEncodingutf8或者建库时用了默认的latin1。解决三步一次性改到位。第一建库时用DEFAULT CHARACTER SET utf8mb4第 3 章的命令里有。第二连接串加characterEncodingutf8。第三application.yml里不要额外设置spring.datasource.sql-script-encoding这样重复的属性容易覆盖默认。改完重启还乱码就用SHOW CREATE TABLE user;确认表的默认字符集。5.5 Docker 装的 MySQL 连不上Connection refused现象docker run启动了 MySQL端口也映射了3306:3306但本机 JDBC 连接报Connection refused或Communications link failure。原因最常见的是端口映射没生效——容器内部 MySQL 监听的是3306但宿主机的3306被本机自带的 MySQL 抢先占用了docker 的端口映射是共享模式原服务占了就映射失败。解决先执行netstat -ano | findstr 3306Windows或lsof -i:3306Linux查谁占了端口。如果是本机装了 MySQL 服务要么停掉本机服务net stop mysql要么把容器映射改成其他端口如3307:3306并同步修改application.yml的 URL。还有一种情况是容器内 MySQL 初始化没完成就开始连docker logs 容器名看到ready for connections再连。5.6 Transactional 不生效同方法内部调用绕过了代理现象下单方法报错后数据库里订单还是插进去了库存也扣了事务没回滚。原因Transactional通过 Spring AOP 代理生效它的调用链是外部对象 → 代理对象 → 目标方法。当你在这个类内部用this.createOrder()调另一个Transactional方法时this是原始对象而不是代理对象事务注解自然被跳过了。解决把需要事务的方法放到不同的 Service 里通过注入的 Bean 调用。比如下单和扣库存都在OrderService你不能再写一个OrderService内部方法去直接调它应该让OrderController调OrderService.createOrder()而createOrder内部调BookService.deductStock()这个BookService是注入的代理对象事务才生效。如果确实要在同类里调用就自己注入ApplicationContext取出代理或者直接用TransactionTemplate手动编程式事务后者更直白适合新手排查。6. 从能跑到高分论文、数据库脚本与文档说明的落地写法6.1 论文骨架七章结构的答辩布局论文是高分的关键程序跑通只能保证及格。我见过太多代码写得不错、论文像流水账的案例最后分数平平。一张相对稳妥的论文结构如下第一章绪论写背景和意义第二章相关技术介绍 Java、Spring Boot、MySQL第三章需求分析画用例图和 ER 图第四章系统设计写架构图加数据库设计第五章系统实现按模块贴核心代码配截图第六章系统测试写测试用例表和结果第七章总结与展望。重点在第三章和第四章用例图要覆盖管理员、买家、卖家三类角色ER 图要和第二章建的表一一对应数据库设计章节里放建表 SQL 和表关系说明这就是“设计与实现”里的“设计”分量。6.2 数据库脚本与文档说明给评委看的三个交付物除了论文数据库脚本和文档说明的规范度也能拉开差距。我会把所有 SQL 文件放在一个database目录下分成01_schema.sql建库建表、02_data.sql初始数据含管理员账号、03_index.sql索引和视图。这样的文件分割方式比一个大而全的init.sql更专业评委会觉得你做工程有章法。文档说明我一般建议写一份部署文档.md内容包括环境版本表、每个配置项的解释、启动步骤、常见错误对照表。这份文档在答辩时直接放在桌面评委提问时你一边翻一边回答比空口说强得多。最后说一个我的血泪经验答辩前一天一定把项目从零部署一遍包括删库、删表、重新执行脚本从头走一遍整个流程。我当年就是图省事跳过这步结果答辩现场换了台电脑MySQL 版本从 8.0 变成 5.7驱动类名报错当场翻车。这个教训让我养成了“每改一次环境就完整跑一遍部署流程”的习惯。源码、数据库、文档三者对齐你就不怕任何关于技术栈和业务逻辑的追问。希望帮到你。本文还有配套的精品资源点击获取
返回列表