claude-code/claude-zh/skills/jpa-patterns/SKILL.md

151 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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<PositionEntity> 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<MarketEntity> findWithPositions(@Param("id") Long id);
```
## 倉儲模式 (Repository Patterns)
```java
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
Optional<MarketEntity> findBySlug(String slug);
@Query("select m from MarketEntity m where m.status = :status")
Page<MarketEntity> findByStatus(@Param("status") MarketStatus status, Pageable pageable);
}
```
- 針對輕量化查詢使用投影 (Projections)
```java
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page<MarketSummary> 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<MarketEntity> 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 問題,並針對讀取/寫入路徑建立合適的索引。