文档站点
Skip to content

JPA 入门(对应 Eloquent) ​

Spring Data JPA 是 Java 世界的 Eloquent:实体映射表、Repository 查库、findById、save…… 概念几乎一一对应。这篇把 JPA 的「实体 + Repository」两件套讲透。

概念对照 ​

EloquentSpring 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

依赖 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

实体类:@Entity + @Table + Lombok ​

对应 Laravel 的 Model:

java
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 不会自动。三个选择:

  1. 数据库默认值(DEFAULT CURRENT_TIMESTAMP,见 迁移 里的建表 SQL)
  2. 手动在 save 前 set
  3. 用 @PrePersist / @PreUpdate 回调(自动填充,最接近 Eloquent)
java
@PrePersist
public void prePersist() {
    createdAt = LocalDateTime.now();
    updatedAt = LocalDateTime.now();
}

@PreUpdate
public void preUpdate() {
    updatedAt = LocalDateTime.now();
}

Repository 接口:核心两件套的第二件 ​

对应 Eloquent Model 的查询能力,Spring Data 帮你根据方法名自动生成 SQL:

java
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 层 ​

java
@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 DESCwhere(...)->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」更早暴露问题。

配置 ​

yaml
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 一样,写操作建议包事务(见 数据库连接配置):

java
@Transactional
public Post create(PostDto dto) { ... }

下一步 ​

面向 PHP / Laravel 开发者的 Spring Boot 中文文档