ARTICLE DETAIL

资讯详情

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

Spring Boot数据库操作实战:从CRUD到事务与连接池调优

Spring Boot数据库操作实战:从CRUD到事务与连接池调优 1. 先想清楚这套数据库方案到底怎么写做 Spring Boot 项目最绕不开的一环就是数据库操作。尤其在课程设计、毕业设计和中小企业后台系统里几乎每个需求都会落到增删改查上。我自己带过不少实习生也帮人改过不少跑不起来的项目发现大家在数据库这块栽跟头通常不是不会写 SQL而是对 Spring Boot 里操纵数据库的几种姿势没有整体概念选型乱、配置错、事务没管好最后查询慢、连接池爆、死锁频发全堆到一起。先把这个场景摆清楚。标题是Spring Boot 实战数据库操作我们就要覆盖从项目初始化、建库建表、实体设计、CRUD 编写到事务和连接池调优的完整链路。目标读者包括三类人一是刚开始学 Spring Boot 的初学者需要一套能复现的入门路径二是正在做校园讲座预约系统企业办公用品管理系统商城这类课程的在校学生需要能落地的技术方案三是刚接手企业项目的初级开发想快速搞清楚常见的坑在哪里。我会用一个贯穿全文的案例来说——地址簿管理。为什么选它因为地址簿刚好能覆盖数据库操作的所有基础形态单表 CRUD、分页查询、批量插入、事务回滚再加一个再普通不过的姓名电话地址字段设计。它在热搜词里也频繁出现使用 spring boot 编写地址簿管理说明这是一个被反复验证过的入门级业务场景拿它做载体最稳妥。先把 Spring Boot 数据库操作的几条路线讲清楚这是整个项目的第一决策点。当前主流的方案有三个Spring JDBC 自带 JdbcTemplate、Spring Data JPAHibernate、MyBatis / MyBatis-Plus。很多初学者一上来就在这三个之间纠结其实它们的定位完全不同。JdbcTemplate 是 Spring 对 JDBC 的轻量封装你写 SQL它管连接的获取和释放、结果集的映射。优点是直观、学习曲线平缓缺点是一切都要手写对于多表复杂查询代码量会迅速膨胀。MyBatis 把 SQL 和 Java 代码解耦灵活性极高适合复杂查询、报表、多表关联的原生 SQL 控场。缺点是配置相对繁琐每一张表都要写 mapper XML 或注解 SQL开发效率不算高。MyBatis-Plus 在这个基础上做了大量增强单表 CRUD 基本不用写 SQL内置分页插件、逻辑删除、自动填充在国内企业项目里非常主流很多课程设计也用它。Spring Data JPA 是 Spring 官方主推的 ORM 方案核心是 Repository 接口通过方法名推导查询让开发者完全不用写 SQL 就能完成大部分单表操作。它的杀手锏是复杂的关联关系管理和自动建表但在多表 join 场景下比较痛苦且一旦遇到冷门 SQL 优化点排查链路较长。我的建议是如果是快速做课程设计或内部管理系统优先考虑 MyBatis-Plus如果是学习 Spring 官方生态JPA 值得仔细啃一遍如果只是写个小工具JdbcTemplate 反而最省心。但不管最后选哪个这篇实战里我会把 JdbcTemplate 和 JPA 各实现一遍核心 CRUD原因后面细说。顺带把Spring、Spring Boot、微服务是什么区别这个高频疑问也解答掉Spring 是个大框架体系Spring Boot 是让 Spring 应用快速启动运行的工具集微服务则是架构风格可以基于 Spring Boot Spring Cloud 来实现。我们在数据库操作层面几乎不需要考虑微服务的事只要把每一步依赖和配置弄明白即可。2. 环境初始化5 分钟把一个能连库的工程跑起来很多人项目跑不起来问题不在业务代码而是工程初始化环节就没搞对。这里先解决最基础的怎么快速创建一个 Spring Boot 项目并且让数据库连接通畅。下面这招我在多台电脑上反复实测哪怕你本地只装了 JDK 和 IDEA5 分钟也能看到Hello Database。第一步打开 IDEA 的 Spring Initializr或者直接浏览器访问 start.spring.io。Group 填 com.exampleArtifact 填 address-bookJava 版本选 8 或 17 都可以。依赖这里重点来了勾选 Spring Web、Spring Data JPA 或 MyBatis按上一节的选型思路来、MySQL Driver、Lombok。如果你想极速起步也可以只勾选 Spring Web 和 MySQL Driver 两个然后手动引入 JdbcTemplate 依赖Spring Boot 会自带 JdbcTemplate 的自动配置。版本选型很关键尤其是做课程设计的同学喜欢图省事选最新版结果踩了版本兼容的坑。目前稳定推荐 Spring Boot 2.7.x JDK 8/11/17这个组合资料最多、周边兼容性最好Spring Boot 3.x 需要 JDK 17如果你用 JDK8 直接启动 3.x 会直接报错。如果非要玩新东西JDK 21 Spring Boot 3.5 还支持虚拟线程性能确实好但建议等基础跑通再折腾。第二步配置 application.yml。这是数据库操作的前线90% 的连接问题都出在这份文件上。一个最基础的 MySQL 配置长这样spring: datasource: url: jdbc:mysql://localhost:3306/address_book?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai这里重点解释三个参数。serverTimezone不设置凌晨八点之前你可能被一个日期字段无法解析的报错卡半小时因为数据库驱动默认按照 UTC 时区来处理时间和你本机相差 8 小时。useSSLfalse在本地环境务必加上否则 MySQL 8 会尝试建立 SSL 连接日志里会多出一大片安全警告看着心烦但不算致命。ddl-auto: update可以自动按实体类更新表结构开发期很香但上线前一定要改成validate或者干脆关掉否则生产库表结构被实体类改动带偏就麻烦了。第三步验证连接。最简单的做法不写任何业务代码在启动类里用CommandLineRunner打印一个数据库连接信息。或者直接写一个 Controller 返回当前系统时间本质上是触发一次连接池初始化。这一步通过说明数据源配置没问题后续操作才有意义。我这里还要强调一个热词场景日志。很多人项目启动卡死在数据库连接那一步但日志级别不对看不到真正原因。可以在配置里临时把 SQL 和连接池日志打开logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace com.zaxxer.hikari: debug看到 HikariPool 成功启动的日志代表数据库连接池已经就绪。看到 Hibernate 打印出的建表 SQL说明 JPA 正在自动生成表结构。这条链路跑通工程初始化的最后一公里就打通了。3. 建表与数据建模Navicat 里一次到位的习惯数据库操作不只是写 Java 代码数据建模反而是地基。如果表设计得稀烂后面写 CRUD 会处处别扭。这里我以 Navicat 为例因为它是国内用得最多的 MySQL 图形化工具课程设计和企业开发都常驻。打开 Navicat 的查询工具把下面这段建表 SQL 直接怼进去执行CREATE TABLE contact ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(50) NOT NULL COMMENT 姓名, phone varchar(20) NOT NULL COMMENT 电话, address varchar(200) DEFAULT NULL COMMENT 地址, email varchar(100) DEFAULT NULL COMMENT 邮箱, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除标记, PRIMARY KEY (id), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT地址簿联系人表;这张表的设计有三个值得琢磨的点。第一datetime字段用数据库默认值来维护创建时间和更新时间Java 代码里就不用每次手动 set省掉一个低级错误源。第二deleted字段是逻辑删除标记业务上删除联系人时只做UPDATE deleted 1不真正 DELETE这样数据可追溯配合 MyBatis-Plus 的逻辑删除功能能无侵入搞定。第三索引只加了一个idx_name因为联系人表的查询频率最高的是按姓名搜索手机号如果经常查询也可以补一个索引但索引不是越多越好每多一个索引插入更新都会多维护一棵 B 树。再说一个热搜词提到的高频需求用 Navicat 给数据库账号配置只读权限。这在企业里很常见给运营或外包人员开一个只能查询、不能增删改的账号。操作位置在 Navicat 的用户面板新建用户时主机填%表示任意主机可连也可以指定内网 IP 段密码按需设置。关键在权限标签页只勾选SELECT其余统统不勾。如果你还想限制某些表就在对象权限里逐个勾选表。重点提醒最好不要给只读账号开通全局权限再试图用库来限制因为 MySQL 的权限模型是分层级叠加的全局权限一旦给了库级限制很难往下收正确做法是从最小粒度开始往上加。实体类这边用 Lombok 简化样板代码。JPA 风格下实体类是这样Data Entity Table(name contact) public class Contact { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false, length 50) private String name; Column(nullable false, length 20) private String phone; Column(length 200) private String address; Column(length 100) private String email; Column(name created_at, insertable false, updatable false) private LocalDateTime createdAt; Column(name updated_at, insertable false, updatable false) private LocalDateTime updatedAt; Column(name deleted, insertable false) private Integer deleted; }insertable false, updatable false这两行很多人不理解它的意思是创建和更新时间完全交给数据库默认值维护Java 实体在 insert 和 update 时不去碰这两个字段避免代码和数据库默认值打架。GeneratedValue(strategy GenerationType.IDENTITY)是告诉你主键靠数据库自增生成插入后 JPA 会自动把生成的 id 回填到实体对象中这个特性在批量导入时特别有用。实体和表之间的映射看似简单但字段类型对应其实有讲究。数据库的datetime对应 Java 的LocalDateTime这是官方推荐的新时间 API绝对不要用java.util.Date否则 JSON 序列化和时区处理能把你折磨到怀疑人生。只要数据库字段带下划线如created_at实体字段用驼峰如createdAt并且开启了 Hibernate 的命名策略或 MyBatis-Plus 的驼峰映射框架会自动完成转换。4. 核心 CRUD 实现从 JdbcTemplate 到 JPA 的写法对比如果说建表是打地基那 CRUD 就是盖楼。这一节我会把同一个地址簿管理场景分别用 JdbcTemplate 和 Spring Data JPA 各实现一遍核心操作。为什么要写两遍因为实操中你会发现很多系统是混合体——简单单表操作用 JPA 省事复杂统计查询用 JdbcTemplate 或 MyBatis 控场。两种姿势都掌握面试和干活都从容。先看 JdbcTemplate 版。它的核心思想是你写 SQL框架负责替换参数和映射结果。一个 Service 方法长这样Service public class ContactServiceJdbc { private final JdbcTemplate jdbcTemplate; public ContactServiceJdbc(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } // 新增 public int add(Contact contact) { return jdbcTemplate.update( INSERT INTO contact(name, phone, address, email) VALUES(?, ?, ?, ?), contact.getName(), contact.getPhone(), contact.getAddress(), contact.getEmail() ); } // 修改 public int update(Contact contact) { return jdbcTemplate.update( UPDATE contact SET phone ?, address ?, email ? WHERE id ? AND deleted 0, contact.getPhone(), contact.getAddress(), contact.getEmail(), contact.getId() ); } // 删除逻辑删除 public int deleteById(Long id) { return jdbcTemplate.update( UPDATE contact SET deleted 1 WHERE id ?, id ); } // 查询单个 public Contact findById(Long id) { ListContact list jdbcTemplate.query( SELECT id, name, phone, address, email, created_at, updated_at FROM contact WHERE id ? AND deleted 0, new Object[]{id}, (rs, rowNum) - { Contact c new Contact(); c.setId(rs.getLong(id)); c.setName(rs.getString(name)); c.setPhone(rs.getString(phone)); c.setAddress(rs.getString(address)); c.setEmail(rs.getString(email)); c.setCreatedAt(rs.getObject(created_at, LocalDateTime.class)); c.setUpdatedAt(rs.getObject(updated_at, LocalDateTime.class)); return c; } ); return list.stream().findFirst().orElse(null); } // 分页查询 public ListContact page(String keyword, int pageNum, int pageSize) { String like % keyword %; int offset (pageNum - 1) * pageSize; return jdbcTemplate.query( SELECT id, name, phone, address FROM contact WHERE deleted 0 AND (name LIKE ? OR phone LIKE ?) ORDER BY updated_at DESC LIMIT ? OFFSET ?, new Object[]{like, like, pageSize, offset}, (rs, rowNum) - { Contact c new Contact(); c.setId(rs.getLong(id)); c.setName(rs.getString(name)); c.setPhone(rs.getString(phone)); c.setAddress(rs.getString(address)); return c; } ); } }这段代码里有几个实战细节必须强调。第一所有 SQL 都用了?占位符参数通过new Object[]传入这是防 SQL 注入的标准姿势。学生项目最常见的低级错误是字符串拼接 SQL比如WHERE name name 一旦姓名里出现单引号轻则报错重则被注入攻击。JdbcTemplate 的占位符机制能直接消灭这个问题。第二删除不是真正的 DELETE而是deleted 1的逻辑删除。这一步在真实业务里很重要因为联系人被误删后还能恢复也保留了操作审计的数据痕迹。后面所有查询条件都记得加deleted 0。第三LIMIT ? OFFSET ?是分页的核心逻辑。(pageNum - 1) * pageSize算偏移量这条公式是分页的灵魂很多人就是栽在第一页数据重复、最后一页数据丢失上——90% 是因为 offset 算错了。再看 JPA 版本。JPA 的写法更贴近业务表达核心是继承JpaRepository接口public interface ContactRepository extends JpaRepositoryContact, Long { // 按姓名模糊查询忽略逻辑删除标记 Query(SELECT c FROM Contact c WHERE c.deleted 0 AND (c.name LIKE %:keyword% OR c.phone LIKE %:keyword%)) PageContact search(Param(keyword) String keyword, Pageable pageable); }Service 层调用Service public class ContactServiceJpa { private final ContactRepository repository; public ContactServiceJpa(ContactRepository repository) { this.repository repository; } // 新增 public Contact add(Contact contact) { return repository.save(contact); } // 修改按持久化上下文自动 UPDATE public Contact update(Contact contact) { return repository.save(contact); } // 逻辑删除 Transactional public void deleteById(Long id) { repository.findById(id).ifPresent(c - { c.setDeleted(1); repository.save(c); }); } // 分页 public PageContact page(String keyword, int pageNum, int pageSize) { Pageable pageable PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, updatedAt)); return repository.search(keyword, pageable); } }JPA 版本有几个经常被误解的操作这里一起说透。save()方法是 JPA 新手最容易困惑的。它在新增和修改之间的切换规则是如果实体的主键 id 为 null则执行 persist插入如果 id 已存在则执行 merge更新。但这里有个坑——如果你把待更新的实体从页面上整个传进来里面字段丢失了JPA 会把你丢了的那些字段直接覆盖为默认值。正确做法是findById先把实体从数据库取出来然后逐个 set 你要改的字段再 save这样既安全又符合业务直觉。我在上文的 update 里只做了save(contact)那是假设前端传回的是完整对象如果只传回了一半字段务必回到 findById 再 merge 的模式。Query注解里用了%:keyword%这是 JPA 的 JPQL 语法。注意和原生 SQL 的区别这里操作的是实体类Contact和实体字段deleted、name不是数据库表名和列名。如果你写的表名是 contact 但实体类名是 Contact在 JPQL 里用反了会直接报错这是从 MyBatis 切到 JPA 的人最常见的转型痛。事务上逻辑删除方法我加了Transactional。为什么因为findById拿到实体后修改字段再 save这是两个数据库操作。没有事务包裹的话如果在修改后、save 前抛异常数据会处于不一致状态。而加了事务JPA 的一级缓存机制会保证整个方法内所有操作要么全提交要么全回滚。注意 JPA 有一个隐藏行为——事务内的实体修改可以不用手动调 save事务提交时脏检查会自动 UPDATE但为了代码清晰很多团队还是保留显式 save 的写法。到这一步你可能已经发现了关键区别JdbcTemplate 让你完全掌控 SQL适合查询复杂、对性能敏感的场景JPA 让日常增删改查的代码量减少一半以上但要求你理解持久化上下文和实体生命周期。一个实战项目里两种方案完全可以共存我见过很多架构做得干净的项目主体用 JPA/MyBatis-Plus统计报表和复杂 join 用 JdbcTemplate 或原生 SQL 兜底各取所长。5. 事务、连接池与日志上线前必须处理的细节CRUD 跑通了只是完成了能用要让系统好用事务管理、连接池调优、日志配置这三个细节是躲不开的。这一节的内容是很多课程设计和初级项目最薄弱的地方但恰恰是面试官最常提问的地方。先说事务。数据库操作中最典型的事务场景是批量给一批联系人打标签假设要插入 500 条记录任何一条插入了其他所有都要同步要么全成、要么全不成。用Transactional是最简单的办法Service public class BatchService { Transactional public void batchAdd(ListContact contacts) { for (Contact c : contacts) { repository.save(c); // 500条循环插入 } } }这段代码的问题也随之而来循环内每条 insert 都要一次数据库往返500 条数据就 500 次网络 IO性能堪忧。实战优化的常用手段是批量插入。使用 JPA 时可以这样做Transactional public void batchAddFast(ListContact contacts) { for (int i 0; i contacts.size(); i) { repository.save(contacts.get(i)); if (i % 100 0) { repository.flush(); // 主动提交当前批次清空一级缓存 repository.clear(); } } }flush()的作用是把当前持久化上下文里的变更同步到数据库clear()则是清空一级缓存。如果不做这两步500 条实体全部塞进一级缓存内存压力大不说最后的隐式 flush 可能触发 Hibernate 的全量检查性能反而更差。这个技巧在课程设计里非常加分因为 90% 的人不会想到控制一级缓存的边界。再讲事务失效的场景这个在面试里被问烂了但在项目里真的天天踩。事务失效的三大经典场景方法被private修饰、同一个类内部方法互相调用、异常被 try-catch 吞掉了。特别是第三个我带着学生做校园讲座预约系统时就遇到过——预约成功后统计人数统计代码抛了异常但事务没回滚因为外层把异常 catch 住并返回了预约失败数据库里预约记录却已经插进去了。正确写法是让事务方法内遇到异常就往外抛让Transactional的回滚机制生效同时设置Transactional(rollbackFor Exception.class)明确告诉 Spring碰到任何异常都回滚因为 Spring 默认只对 RuntimeException 回滚对 checked exception 不回滚。接着是连接池调优。Spring Boot 2.x 默认使用 HikariCP它是业内性能数一数二的连接池。默认配置很多场景其实够用但高并发系统要手动调几个参数spring: datasource: hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000maximum-pool-size不是越大越好。我见过一个项目把连接池上限设成 200MySQL 默认的max_connections是 151结果连接还没被业务用完数据库先拒绝服务了。合理的做法是基于实际并发量测算公式是并发请求数 × 单次请求平均占用连接时间 ÷ 1000ms。一个简单的经验值是常规内部管理系统 10~20 就够了高并发网关服务可以到 30~50不要再高。连接池不是配置好就完事还要监控活跃连接数。如果active长期接近maximum-pool-size说明连接释放有泄漏排查方向是检查代码里是否手动获取了连接但没归还。日志这一节顺着前面的热词spring boot 日志展开。生产环境里日志是数据库问题的第一手证据。建议把 SQL 日志单独拆出来用一个独立的 logger 输出到独立文件这样排查慢 SQL 和死锁时不用在整个应用日志里大海捞针。MyBatis-Plus 的话在application.yml里加mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl就能把 SQL 和参数打到控制台。JPA 的话设置spring.jpa.show-sql: true可以打印 SQL但要完整看到参数还得配合logging.level.org.hibernate.type.descriptor.sql.BasicBinder: trace。上线时记得把show-sql关掉改成通过日志框架的级别控制因为show-sql是 Hibernate 的反射开关性能上和标准日志不在一个量级。补充一个容易被忽视的配置数据库连接空闲超时和 MySQL 的wait_timeout要协调好。MySQL 默认wait_timeout是 8 小时如果 HikariCP 的max-lifetime超过它连接会被 MySQL 主动断开而连接池不知道下次拿到的就是一堆死连接表现就是项目跑一会儿后突然报Communications link failure。给 HikariCP 设置max-lifetime: 180000030分钟就比 MySQL 的 8 小时短很多可以从源头避开这个问题。这个坑在局域网开发环境里不冒头一旦部署到公司服务器或云上特别容易半夜出故障值班的人毫无头绪。6. 常见问题与排查技巧我踩过的坑你大概率也会踩数据库操作最磨人的不是写代码而是报错之后不知道怎么定位。我把这几年被问得最多、也是自己踩过的坑整理成一份排查表标注了现象、根因和解法直接收藏照着查就行。现象根因解法启动报Access denied for user rootlocalhost密码错或权限不对核对 application.yml 里的密码用 Navicat 手动连一次确认账号可用报Unknown database address_book数据库还没创建在 Navicat 里先执行CREATE DATABASE address_book DEFAULT CHARACTER SET utf8mb4;报Server returns invalid timezone. Go to Advanced tab and set serverTimezoneMySQL 时区设置问题连接 URL 加serverTimezoneAsia/Shanghai或者执行SET GLOBAL time_zone08:00;插入中文变成问号数据库或表字符集不是 utf8mb4建库建表全部指定DEFAULT CHARSETutf8mb4并在 URL 上加characterEncodingutf8JPA 启动时报Unable to build Hibernate SessionFactory实体类和表结构不匹配检查Table(namecontact)是否写对字段名是否和表列对应查询报Column deleted does not exist表里没做逻辑删除字段但代码条件里有给表加deleted字段或在 JPQL/SQL 中去掉该条件Autowired注入 Repository 报NullPointerException空指针出现在构造方法里或 Bean 没被扫描到检查启动类是否在 controller/service 包外面或者 service 是否缺失Service注解Communications link failure连接被 MySQL 断开连接池还留着死连接给连接池设置max-lifetime小于 MySQL 的wait_timeout分页出现重复数据排序字段有重复值分页不稳定排序字段后面追加主键比如ORDER BY updated_at DESC, id DESC这里挑两个典型展开说。第一个是 Bean 注入失败。热词里有spring boot bean注入控制很多同学在字段上用Autowired明明写了运行却报No qualifying bean of type ContactService available。原因大概率是 Service 类没有加Service或者启动类位置不对。Spring Boot 默认从启动类所在包开始扫描如果你的启动类在com.example.addressbookService 却放在com.example.service里不在同包或子包下Spring 根本扫不到。解法是启动类上加SpringBootApplication(scanBasePackages com.example)让扫描范围扩大或者干脆把工程目录结构规范成com.example.addressbook.controller、com.example.addressbook.service这种统一前缀。这是架构规范问题不值得在代码层面打补丁。第二个是我自己折腾最久的一个问题JPA 懒加载报错failed to lazily initialize a collection。场景在前面的企业办公用品管理系统里特别典型查询一条采购单记录顺便想拿到关联的审批记录列表结果前端序列化时报错异常信息都指向 Hibernate 的 LazyInitializationException。原因很简单事务在 Service 层结束了实体回到 Controller 层此时再访问懒加载集合Session 已经关闭Hibernate 直接炸给你看。解法有三种第一种是改 EAGER 懒加载但会污染关联查询的性能不推荐第二种是在 Service 内把需要的数据提前初始化比如循环里强制访问一次关联集合属于野路子第三种最干净——用 DTO 在 Service 层完成数据组装Controller 只面向 DTO完全不暴露 JPA 的懒加载代理。第三种方案虽然多写一点转换代码但彻底摆脱了序列化框架对实体关联的干扰是长期项目最稳的做法。一个实操小技巧排查 SQL 问题时优先把 SQL 打印出来。热词里提到的spring boot 集成 web socket yml 配置这类不相关的问题先放一边你只需要记住凡是数据库操作有异常第一步永远是看 Spring 实际执行的那条 SQL 长什么样参数是什么。因为代码里写的 JPQL 和最终生成的 SQL 往往有差异尤其 JPA 会自动拼表名、别名、字段一旦条件顺序不对看生成的 SQL 就能一眼定位。这条规则帮我省下了大量摸索时间强烈建议你养成习惯。总结一下实战心得。数据库操作这个主题看起来是增删改查四个方法但真正影响项目成败的其实是你对配置的理解、对事务边界的把握、对连接池参数的敏感度以及对异常日志的执着。我在带项目的过程中反复体会到把环境初始化做扎实把建表习惯养成固定套路把事务失效的坑提前写清楚比多写十个 CRUD 方法都值钱。如果你正在做课程设计无论题目是校园讲座预约、商城、办公用品管理还是地址簿这套从配置到 CRUD 再到排查的链路都是通用的按这个顺序走一遍项目的地基就不会歪。
返回列表