Spring Boot + JPA + MySQL:前端转全栈学 JPA 的实践记录
这篇是前端转全栈 从 0 上手 JPA 的全过程,全是踩过的坑 + 真实跑通的代码。
0. 前端转后端视角:JPA 是什么
JPA = Java 世界的 ORM(对象关系映射) 。和你写 Node 后端时用的 TypeORM / Sequelize 是同类东西:操作对象就操作数据库,不用手写单表 SQL。
先给三个定位,帮你快速建立直觉:
| 你熟悉的 | Java 里的 | 一句话定位 |
|---|---|---|
SELECT * FROM user WHERE name='xx'(原生 SQL) |
JDBC | 最底层:你自己拼 SQL,自己拼连接,自己处理结果集 |
| TypeORM / Sequelize | JPA(Spring Data JPA) | ORM:把数据库映射成对象,不用写单表 SQL |
| --- | MyBatis / MyBatis-Plus | SQL 友好:帮你写 SQL/XML,单表 CRUD 也不用写 SQL,复杂 SQL 你可以自己写 |
一句话结论(后面第 6 节详细对比):做 CRUD 多、业务规整的业务(用户、权限、字典),JPA 爽;多表联查/复杂统计 SQL,MyBatis 灵活。
1. 项目长什么样?
先把项目目录摆出来,和 study-h2(MyBatis-Plus 版)对齐,方便对比:
bash
study-jpa/
pom.xml # Maven 依赖
src/main/
java/com/wy/study/jpa/
JPAApp.java # 启动类(main 在这里)
controller/
JPARoleController.java # HTTP 接口(对外面)
entity/
JPARoleEntity.java # 角色表对应的 Java 对象(核心!)
repository/
JPARoleRepository.java # 数据访问层(JPA 的 Mapper)
service/
JPARoleService.java # 业务层
resources/
application.yml # 数据库 + JPA 配置
和 MyBatis-Plus 的项目结构几乎一样(Controller/Service/Entity),只是 Mapper 改叫 Repository。
案例代码地址:gitee.com/banmaxiaoba...
2. 第一步:加依赖(JPA 是官方 Starter,比 MyBatis 更"正统")
父 pom 统一管版本,子模块只写坐标:
xml
<dependencies>
<!-- Spring Data JPA:官方 ORM Starter,自带 Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
就这两个。Spring Data JPA 内部自动依赖 Hibernate(实现 ORM 的底层),你不用自己加 Hibernate 依赖。
3. 第二步:连 MySQL + 配 JPA 关键参数
打开 src/main/resources/application.yml:
yaml
server:
port: 9204 # 端口(和 study-h2 的 9203 错开)
servlet:
context-path: /jpa # 所有接口前面都要加 /jpa
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
jpa:
show-sql: true # 强制开:控制台打印 Hibernate 生成的 SQL,排错神器
open-in-view: false # 必须关:Spring 默认会开,生产会炸(懒加载问题)
hibernate:
ddl-auto: none # 建表策略,后面讲 4 种模式
properties:
hibernate:
format_sql: true # SQL 格式化,好看
use_sql_comments: true # 加注释,知道 SQL 来自哪个方法
重要:ddl-auto 四种模式(你可能只见过 update)
| 模式 | 作用 | 场景 |
|---|---|---|
none |
完全不管 DDL | 生产环境首选,用 Flyway/Liquibase 管表 |
update |
自动更新表结构(加列不加删列) | 开发环境省事,改实体自动改表 |
create |
每次启动重建表(数据清零) | 临时 Demo 用,不敢生产 |
validate |
只校验实体和表是否匹配 | 启动时检查,不修改表 |
重要:命名策略(踩过的坑)
SpringPhysicalNamingStrategy(Spring Boot 默认)会把实体的 camelCase 转 snake_case:
- 实体字段
createTime数据库列create_time - 实体字段
roleId数据库列role_id
如果你数据库表是 驼峰列名 (比如旧项目、别的团队建的表),就要在实体每个字段加 @Column(name="createTime"),或者全局关掉命名策略(我用了后者)。
4. 重点来了:JPA 的三层结构(和 MyBatis 分层一致,但 Repository 更爽)
还是"点菜类比",和 study-h2 的服务员/领班/厨师对齐,一眼明白:
markdown
Controller(服务员)
接收 HTTP 请求,转给 Service
对应:JPARoleController.java
调用
Service(后厨领班)
业务流程控制,事务、缓存、发消息
对应:JPARoleService.java
调用
Repository(厨师,JPA 版)
继承 JpaRepository,**不用写单表 SQL!**
对应:JPARoleRepository.java
和 MyBatis-Plus 的核心不同:两者继承一下都有单表 CRUD,但拿 SQL 的方式不一样:
- JPA:ORM 黑盒,Hibernate 自动生成 SQL,你看不到也管不了(可开
show-sql看,复杂查询用@Query写 JPQL/原生 SQL) - MyBatis-Plus:SQL 友好,默认 CRUD 也不用写,但复杂 SQL 你可以直接写注解
@Select或 XML,所见即所得
各自适合什么场景?
sql
你的业务:
├─ 主要是用户、权限、字典这种 CRUD 多、业务规整 → JPA(黑盒自动 SQL 够了)
├─ 有大量多表联查、复杂统计 SQL(报表、大数据)→ MyBatis-Plus(SQL 友好)
└─ 两者都有 → 同项目可以混用:简单表 JPA,复杂查询 MyBatis(Spring Boot 两者兼容)
派生查询(方法名即 SQL)会生成什么样的真实 SQL?
Repository 里写一个方法,Hibernate 自动拼 SQL,不用你写一行 SQL:
1. JPARoleEntity findByName(String name);
sql
-- Hibernate 自动生成(带表别名,MySQL limit 1):
select jparoleent0_.id as id1_0_, jparoleent0_.code as code2_0_,
jparoleent0_.create_time as create_t3_0_, jparoleent0_.description as descript4_0_,
jparoleent0_.modify_time as modify_t5_0_, jparoleent0_.name as name6_0_,
jparoleent0_.status as status7_0_
from study_jpa_role jparoleent0_
where jparoleent0_.name = ?
limit ?
-- 最终参数:name='系统管理员', limit=1
2. List<JPARoleEntity> findRoleEntitiesByName(String name);
sql
-- 和上面一样,但没有 limit(因为返回 List):
select jparoleent0_.id as id1_0_, ...
from study_jpa_role jparoleent0_
where jparoleent0_.name = ?
3. 模糊查询(@Query 写 JPQL)
java
@Query("select e from JPARoleEntity e where e.code like concat('%', :code, '%')")
List<JPARoleEntity> findByCodeLike(@Param("code") String code);
sql
-- JPQL 自动转原生 SQL(写的是实体名 JPARoleEntity,转成表名 study_jpa_role):
select jparoleent0_.id as id1_0_, ...
from study_jpa_role jparoleent0_
where jparoleent0_.code like concat('%', ?, '%')
-- 参数:code='te' → 匹配 teacher
注意:Hibernate 生成的 SQL 带很长的别名(
jparoleent0_、id1_0_),这是它的内部实现,不影响 MySQL 执行。开show-sql=true+format_sql=true可以在控制台看到。