下载工作台
Spring Boot Web 开发

Spring Data JPA 与实体建模

试读上半部分 · 解锁后可读全文

第 5 章 · Spring Data JPA 与实体建模

本章目标:使用 @Entity 定义商品与分类;编写 JpaRepository 接口实现 CRUD;掌握 一对多 / 多对一 关系映射与懒加载注意点;配置 H2/MySQL 数据源;了解 Flyway 数据库版本迁移;将 ch02~ch04 的内存数据替换为持久化层;对照 shop-demo Django ORM 说明异同。

学时建议:6~7 小时(含 2.5 小时跟练)

前置:完成 spring-boot-web ch04java-dev ch11(JDBC/SQL);django-web ch05 Model 章节更佳。


5.1 场景说明:从内存 List 到数据库

ch02 的 InMemoryProductCatalogService 重启即丢失。运营后台需要持久化商品与分类,与 shop-demoProductCategory 模型对齐(教学虚构字段)。

层次shop-demoshop-spring-demo
模型models.Product@Entity Product
查询Product.objects.filter(...)productRepository.findBy...
迁移makemigrations / migrateFlyway
数据库SQLite / PostgreSQLH2 开发 / MySQL 联调

API 仍经虚构 https://api.example.com 对外;数据库连接串仅用本地或占位 hostname,勿写真实生产凭证


5.2 依赖与数据源

5.2.1 pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

5.2.2 application-dev.yml

spring:
  datasource:
    url: jdbc:h2:mem:shop_spring;DB_CLOSE_DELAY=-1;MODE=MySQL
    driver-class-name: org.h2.Driver
    username: sa
    password:
  h2:
    console:
      enabled: true
      path: /h2-console
  jpa:
    hibernate:
      ddl-auto: validate   # 配合 Flyway,勿用 create-drop 上生产
    show-sql: true
    properties:
      hibernate:
        format_sql: true
ddl-auto含义环境
create-drop启动建表、关闭删表仅本地实验
update自动改表(不可靠)不推荐生产
validate只校验与 DB 一致配合迁移工具
none不管理 schema完全交给 Flyway

Django:migrate 由迁移文件驱动;Spring 推荐 validate + Flyway 而非 ddl-auto=update


5.3 实体 Category 与 Product

5.3.1 Category

package com.example.shop.catalog.entity;

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "categories")
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String name;

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Product> products = new ArrayList<>();

    protected Category() {}

    public Category(String name) {
        this.name = name;
    }

    public Long getId() { return id; }
    public String getName() { return name; }
    public List<Product> getProducts() { return products; }
}

5.3.2 Product

@Entity
@Table(name = "products", indexes = {
        @Index(name = "idx_product_sku", columnList = "sku", unique = true)
})
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 32)
    private String sku;

    @Column(nullable = false, length = 128)
    private String name;

    @Column(nullable = false, precision = 12, scale = 2)
    private BigDecimal price;

    @Column(nullable = false)
    private int stock = 0;

    @Column(nullable = false)
    private boolean active = true;

    @Column(nullable = false, updatable = false)
    private Instant createdAt = Instant.now();

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "category_id")
    private Category category;

    protected Product() {}

    public Product(String sku, String name, BigDecimal price, Category category) {
        this.sku = sku;
        this.name = name;
        this.price = price;
        this.category = category;
    }
}

5.3.3 与 Django Model 对照

JPADjango
@Entitymodels.Model
@Id @GeneratedValueAutoField primary key
@ColumnCharField / DecimalField
@ManyToOneForeignKey
@OneToMany(mappedBy=)related_name 反向
FetchType.LAZY默认 lazy FK
@Table(indexes=)Meta.indexes

5.4 Repository 接口

public interface ProductRepository extends JpaRepository<Product, Long> {

    Optional<Product> findBySku(String sku);

    Page<Product> findByActiveTrueAndNameContainingIgnoreCase(String name, Pageable pageable);

    Page<Product> findByActiveTrue(Pageable pageable);
}
public interface CategoryRepository extends JpaRepository<Category, Long> {
    Optional<Category> findByName(String name);
}

无需实现类——Spring Data 在运行时生成代理。

Django ORMSpring Data JPA
Product.objects.get(sku=...)findBySku
filter(is_active=True)findByActiveTrue...
[:20] 切片Pageable(ch07)
Q() 复杂查询@Query JPQL / Specification

5.4.1 自定义 @Query 示例

@Query("SELECT p FROM Product p JOIN FETCH p.category WHERE p.active = true")
List<Product> findActiveWithCategory();

JOIN FETCH 一次加载关联分类,避免列表页 N+1。


以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。