JPA 入门(对应 Eloquent)
Spring Data JPA 是 Java 世界的 Eloquent:实体映射表、Repository 查库、
findById、save…… 概念几乎一一对应。这篇把 JPA 的「实体 + Repository」两件套讲透。
概念对照
| Eloquent | Spring Data JPA |
|---|---|
Model(继承 Eloquent) | 实体类(@Entity + @Table) |
| 迁移建表 | Flyway 建表(JPA 不管表结构) |
Post::where(...)->get() | postRepository.findByXxx(...) |
Post::find($id) | postRepository.findById(id) |
Post::create($data) | postRepository.save(entity) |
$post->save() | postRepository.save(entity) |
Post::delete() | postRepository.delete(entity) |
Eloquent 的 $fillable | 手动 new + set 字段(无魔术赋值) |
hasMany / belongsTo | @OneToMany / @ManyToOne |
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>实体类:@Entity + @Table + Lombok
对应 Laravel 的 Model:
package com.example.blog.post;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增主键
private Long id;
@Column(nullable = false, length = 100)
private String title;
@Column(nullable = false)
private String content;
@Column(name = "user_id")
private Long userId;
private Integer status = 1;
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// @Data 自动生成 getter/setter,等价于手写一堆方法
}| 注解 | 作用 | Eloquent 对应 |
|---|---|---|
@Entity | 声明这是 JPA 实体 | 继承 Model |
@Table(name = "posts") | 映射到哪张表 | protected $table = 'posts' |
@Id | 主键 | — |
@GeneratedValue(strategy = IDENTITY) | 自增主键 | $incrementing = true |
@Column(name = "user_id") | 字段名映射 | — |
@Column(nullable = false) | 非空 | migration 的 nullable |
时间字段谁来赋值?
Eloquent 自动维护 created_at / updated_at;JPA 不会自动。三个选择:
- 数据库默认值(
DEFAULT CURRENT_TIMESTAMP,见 迁移 里的建表 SQL) - 手动在 save 前
set - 用
@PrePersist/@PreUpdate回调(自动填充,最接近 Eloquent)
@PrePersist
public void prePersist() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
updatedAt = LocalDateTime.now();
}Repository 接口:核心两件套的第二件
对应 Eloquent Model 的查询能力,Spring Data 帮你根据方法名自动生成 SQL:
package com.example.blog.post;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface PostRepository extends JpaRepository<Post, Long> {
// 方法名即查询:Spring Data 解析生成 SQL
List<Post> findByStatus(int status);
Optional<Post> findById(Long id);
List<Post> findByUserId(Long userId);
boolean existsByTitle(String title);
long countByStatus(int status);
}TIP
JpaRepository<Post, Long> 里的两个泛型:实体类型 + 主键类型。它自带 findAll()、findById(id)、save(entity)、deleteById(id)、count() 等基础方法,直接就能用,不用写实现。
使用 Repository:Service 层
@Service
public class PostService {
private final PostRepository postRepository;
public PostService(PostRepository postRepository) {
this.postRepository = postRepository;
}
// 类似 Post::find($id)
public Post get(Long id) {
return postRepository.findById(id)
.orElseThrow(() -> BusinessException.notFound("帖子不存在: " + id));
}
// 类似 Post::create($data)
public Post create(PostDto dto) {
Post post = new Post();
post.setTitle(dto.getTitle());
post.setContent(dto.getContent());
return postRepository.save(post);
}
// 类似 Post::where('status', 1)->get()
public List<Post> listByStatus(int status) {
return postRepository.findByStatus(status);
}
// 类似 $post->delete()
public void delete(Long id) {
postRepository.deleteById(id);
}
}与 Eloquent 的关键差异:findById 返回 Optional
Eloquent 的 find($id) 查不到返回 null;JPA 的 findById 返回 Optional<Post>,必须显式处理「查不到」(orElseThrow 或 orElse)。这强制你处理空值,比 PHP 的「静默 null」更安全,但要适应。
常见方法名 → SQL 对照
| 方法名 | 生成的 SQL 行为 | Eloquent 对应 |
|---|---|---|
findByTitle(String title) | WHERE title = ? | where('title', $t) |
findByTitleContaining(String kw) | WHERE title LIKE %kw% | where('title','like',"%$kw%") |
findByStatusOrderByCreatedAtDesc(int s) | WHERE status=? ORDER BY created_at DESC | where(...)->orderByDesc(...) |
findTop5ByStatus(int s) | LIMIT 5 | ->limit(5) |
findByUserIdAndStatus(Long uid, int s) | WHERE user_id=? AND status=? | 多 where |
existsByTitle(String t) | EXISTS | ->exists() |
countByStatus(int s) | COUNT(*) | ->count() |
方法名解析规则
findBy + 字段名 + 可选的条件关键字(And/Or/Containing/OrderBy...)。规则不复杂但严格,写错(字段名拼错)启动就报错 —— 这比 Eloquent 的「写错动态拼 SQL」更早暴露问题。
配置
spring:
jpa:
hibernate:
ddl-auto: none # 不要用 JPA 自动建表!表结构交给 Flyway
open-in-view: false
show-sql: true # 开发时打印 SQL(生产关掉)ddl-auto 的坑
| 值 | 行为 |
|---|---|
none | 不管表结构(生产必选,配 Flyway) |
update | 自动建/改表(危险:可能误改生产表,本地 demo 可用) |
create-drop | 每次启动重建(只适合内存库测试) |
生产必须 none + Flyway 管表结构。用 update 等于把数据库结构交给 ORM 自动改,Laravel 社区骂的「migration 不用的坏习惯」在 Java 里同样危险。
open-in-view: false 为什么建议关
JPA 默认 open-in-view: true,事务结束后 Session 还开着,Controller 层懒加载才不会报错。但长连接持库有性能隐患。改成 false + 在 Service 事务内把所有数据查好,是社区推荐配置。关掉后若在 Controller 懒加载会报 LazyInitializationException,逼你把查询逻辑收进 Service —— 这是好事。
事务
和 Eloquent 一样,写操作建议包事务(见 数据库连接配置):
@Transactional
public Post create(PostDto dto) { ... }下一步
- JPA 关联关系:hasMany / belongsTo 的 Java 版
- JPA 查询方法:
@Query写复杂 SQL - JPA 序列化与 DTO:解决「实体直接返回 JSON」的坑