--- name: jpa-patterns description: 用於 Spring Boot 的 JPA/Hibernate 模式,涵蓋實體設計、關聯、查詢優化、事務、審計 (Auditing)、索引、分頁及連接池。 --- # JPA/Hibernate 模式 (JPA/Hibernate Patterns) 用於 Spring Boot 中的資料建模、倉儲實作與效能調校。 ## 何時啟用 - 設計 JPA 實體 (Entities) 與資料表映射 (Table Mappings)。 - 定義關聯 (@OneToMany, @ManyToOne, @ManyToMany)。 - 優化查詢(防止 N+1 問題、抓取策略、投影 Projections)。 - 配置事務 (Transactions)、審計或軟刪除 (Soft Deletes)。 - 建立分頁、排序或自定義倉儲方法。 - 調校連接池 (HikariCP) 或二級快取。 ## 實體設計 (Entity Design) ```java @Entity @Table(name = "markets", indexes = { @Index(name = "idx_markets_slug", columnList = "slug", unique = true) }) @EntityListeners(AuditingEntityListener.class) public class MarketEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 200) private String name; @Column(nullable = false, unique = true, length = 120) private String slug; @Enumerated(EnumType.STRING) private MarketStatus status = MarketStatus.ACTIVE; @CreatedDate private Instant createdAt; @LastModifiedDate private Instant updatedAt; } ``` 啟用審計功能: ```java @Configuration @EnableJpaAuditing class JpaConfig {} ``` ## 關聯與防止 N+1 問題 ```java @OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true) private List positions = new ArrayList<>(); ``` - 預設使用延遲加載 (Lazy Loading);必要時在查詢中加入 `JOIN FETCH`。 - 避免在集合 (Collections) 上使用 `EAGER`(預期加載);在讀取路徑上優先使用 DTO 投影。 ```java @Query("select m from MarketEntity m left join fetch m.positions where m.id = :id") Optional findWithPositions(@Param("id") Long id); ``` ## 倉儲模式 (Repository Patterns) ```java public interface MarketRepository extends JpaRepository { Optional findBySlug(String slug); @Query("select m from MarketEntity m where m.status = :status") Page findByStatus(@Param("status") MarketStatus status, Pageable pageable); } ``` - 針對輕量化查詢使用投影 (Projections): ```java public interface MarketSummary { Long getId(); String getName(); MarketStatus getStatus(); } Page findAllBy(Pageable pageable); ``` ## 事務 (Transactions) - 在服務層方法加上 `@Transactional` 註釋。 - 在讀取路徑上使用 `@Transactional(readOnly = true)` 以優化效能。 - 謹慎選擇傳播 (Propagation) 行為;避免過長的事務。 ```java @Transactional public Market updateStatus(Long id, MarketStatus status) { MarketEntity entity = repo.findById(id) .orElseThrow(() -> new EntityNotFoundException("找不到指定的市場資料")); entity.setStatus(status); return Market.from(entity); } ``` ## 分頁 (Pagination) ```java PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending()); Page markets = repo.findByStatus(MarketStatus.ACTIVE, page); ``` 對於游標式 (Cursor-like) 分頁,應在具備排序的 JPQL 中增加 `id > :lastId` 條件。 ## 索引與效能 - 為常用的過濾欄位建索引(如 `status`, `slug`, 外鍵)。 - 使用符合查詢模式的複合索引(例如:`status, created_at`)。 - 避免使用 `select *`;僅投影必要的欄位。 - 批次寫入:配合 `saveAll` 並設置 `hibernate.jdbc.batch_size`。 ## 連接池 (HikariCP) 推薦配置屬性: ``` spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.validation-timeout=5000 ``` 針對 PostgreSQL LOB 處理: ``` spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true ``` ## 快取 (Caching) - 一級快取 (1st-level cache) 是基於 EntityManager 的;避免橫跨事務保存實體。 - 對於讀取密集的實體,可考慮啟用二級快取,但須謹慎驗證其清理 (Eviction) 策略。 ## 資料遷移 (Migrations) - 務必使用 Flyway 或 Liquibase;在生產環境中絕不要依賴 Hibernate 的 auto DDL。 - 保持遷移腳本具備冪等性 (Idempotent) 且採用增量式修改;未經計畫絕不隨意刪除欄位。 ## 測試資料存取層 - 優先使用 `@DataJpaTest` 配合 Testcontainers 來模擬生產環境。 - 使用日誌驗證 SQL 效率:將 `logging.level.org.hibernate.SQL` 設為 `DEBUG`,並將 `logging.level.org.hibernate.orm.jdbc.bind` 設為 `TRACE` 以觀察參數值。 **請記住**:保持實體簡鍊、查詢意圖明確並縮短事務時長。透過抓取策略與投影防止 N+1 問題,並針對讀取/寫入路徑建立合適的索引。