spring-data-jpa
Spring Data JPAを活用し、最新のベストプラクティスに沿ってリポジトリやエンティティを実装し、高度なクエリやパフォーマンス最適化まで実現することで、効率的なデータアクセス層を構築するSkill。
📜 元の英語説明(参考)
Implement Spring Data JPA repositories, entities, and queries following modern best practices. Use for creating repositories (only for aggregate roots), writing queries (@Query, DTO projections), custom repositories (Criteria API, bulk ops), CQRS query services, entity relationships, and performance optimization. Covers patterns from simple repositories to advanced CQRS with detailed anti-patterns guidance.
🇯🇵 日本人クリエイター向け解説
Spring Data JPAを活用し、最新のベストプラクティスに沿ってリポジトリやエンティティを実装し、高度なクエリやパフォーマンス最適化まで実現することで、効率的なデータアクセス層を構築するSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o spring-data-jpa.zip https://jpskill.com/download/8069.zip && unzip -o spring-data-jpa.zip && rm spring-data-jpa.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/8069.zip -OutFile "$d\spring-data-jpa.zip"; Expand-Archive "$d\spring-data-jpa.zip" -DestinationPath $d -Force; ri "$d\spring-data-jpa.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
spring-data-jpa.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
spring-data-jpaフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Spring Data JPA の実装
重要なルール
すべてのエンティティに対してリポジトリを作成してはいけません。必ず、集約のルートに対してのみリポジトリを作成してください。
複雑なクエリメソッド名を使用しないでください。自明でないクエリには、必ず @Query を使用してください。
安易に save() を使用しないでください。必ず、persist と merge のセマンティクスを理解してください (Vlad Mihalcea のガイダンスを参照)。
ステップ 1: リポジトリのニーズを特定する
以下の点を考慮します。
- これは集約のルートですか? - 集約のルートのみがリポジトリを持ちます。
- クエリの複雑さは? - 単純な検索ですか、それとも複雑なフィルタリングですか?
- 読み取り vs 書き込み? - コマンド (書き込み) ですか、それともクエリ (読み取り) ですか?
- パフォーマンスが重要ですか? - 大規模なデータセット、ページネーション、またはプロジェクションですか?
ステップ 2: パターンを選択する
| パターン | いつ | 読み取り |
|---|---|---|
| シンプルなリポジトリ | 基本的な CRUD、1〜2 個のカスタムクエリ | - |
| @Query リポジトリ | 複数のフィルター、結合、ソート | references/query-patterns.md |
| DTO プロジェクション | 読み取り専用、パフォーマンスが重要な場合 | references/dto-projections.md |
| カスタムリポジトリ | 複雑なロジック、バルクオペレーション、Criteria API | references/custom-repositories.md |
| CQRS クエリサービス | 読み取り/書き込みの分離、複数のプロジェクション | references/cqrs-query-service.md |
決定基準:
| ニーズ | シンプル | @Query | DTO | カスタム | CQRS |
|---|---|---|---|---|---|
| 基本的な CRUD | ✅ | ✅ | ❌ | ✅ | ✅ |
| カスタムクエリ | ❌ | ✅ | ✅ | ✅ | ✅ |
| 最高のパフォーマンス | ✅ | ✅ | ✅✅ | ✅✅ | ✅✅ |
| 複雑なロジック | ❌ | ❌ | ❌ | ✅ | ✅ |
| 読み取り/書き込みの分離 | ❌ | ❌ | ✅ | ✅ | ✅✅ |
ステップ 3: リポジトリを実装する
シンプルなリポジトリ
基本的な検索 (1〜2 個のプロパティ) の場合:
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
Optional<ProductEntity> findByCode(String code);
List<ProductEntity> findByStatus(ProductStatus status);
}
アセット: 既存のエンティティとリポジトリのパターンを使用します。
@Query リポジトリ
3 つ以上のフィルター、結合、または可読性が必要な場合。参照: references/query-patterns.md
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
@Query("""
SELECT DISTINCT o
FROM OrderEntity o
LEFT JOIN FETCH o.items
WHERE o.userId = :userId
ORDER BY o.createdAt DESC
""")
List<OrderEntity> findUserOrders(@Param("userId") Long userId);
}
アセット: assets/query-repository.java - 例を含む完全なテンプレート
DTO プロジェクション
読み取り専用で、パフォーマンスが重要なクエリの場合。参照: references/dto-projections.md
public record ProductSummary(Long id, String name, BigDecimal price) {}
@Query("""
SELECT new com.example.ProductSummary(p.id, p.name, p.price)
FROM ProductEntity p
WHERE p.status = 'ACTIVE'
""")
List<ProductSummary> findActiveSummaries();
アセット: assets/dto-projection.java - レコード、インターフェース、ネイティブクエリ
カスタムリポジトリ
Criteria API、バルクオペレーションの場合。参照: references/custom-repositories.md
// 1. カスタムインターフェース
public interface ProductRepositoryCustom {
List<ProductEntity> findByDynamicCriteria(SearchCriteria criteria);
}
// 2. 実装 (名前は <Repository>Impl である必要があります)
@Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
// Criteria API を使用した実装
}
// 3. メインリポジトリは両方を拡張します
public interface ProductRepository extends JpaRepository<ProductEntity, Long>,
ProductRepositoryCustom {
Optional<ProductEntity> findBySku(String sku);
}
アセット: assets/custom-repository.java - 完全なパターン
CQRS クエリサービス
Tomato/DDD アーキテクチャの場合。参照: references/cqrs-query-service.md
// リポジトリ (パッケージプライベート) - 書き込みのみ
interface ProductRepository extends JpaRepository<ProductEntity, ProductId> {
Optional<ProductEntity> findBySku(ProductSKU sku);
}
// QueryService (パブリック) - 読み取りのみ
@Service
@Transactional(readOnly = true)
public class ProductQueryService {
private final JdbcTemplate jdbcTemplate;
public List<ProductVM> findAllActive() {
return jdbcTemplate.query("""
SELECT id, name, price FROM products
WHERE status = 'ACTIVE'
""",
(rs, rowNum) -> new ProductVM(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")
)
);
}
}
アセット: assets/query-service.java - JdbcTemplate を使用した完全な CQRS パターン
ステップ 4: エンティティの関係
詳細なガイダンスについては、参照: references/relationships.md
簡単なパターン:
// ✅ 良い: @ManyToOne (最も一般的)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
// ✅ 代替案: ID のみを使用する (疎結合)
@Column(name = "product_id", nullable = false)
private Long productId;
// ❌ 回避: @OneToMany (代わりに多側のクエリを使用する)
// 代わりに: List<OrderItem> items = itemRepository.findByOrderId(orderId);
// ❌ 絶対にしない: @ManyToMany (代わりに結合エンティティを作成する)
@Entity
public class Enrollment {
@ManyToOne private Student student;
@ManyToOne private Course course;
private LocalDate enrolledAt;
}
アセット: assets/relationship-patterns.java - 例を含むすべての関係タイプ
ステップ 5: パフォーマンスの最適化
完全なチェックリストについては、参照: references/performance-guide.md
重要な最適化:
-
N+1 クエリを防ぐ:
// JOIN FETCH を使用する @Query("SELECT o FROM Order o JOIN FETCH o.customer") List<Order> findWithCustomer(); // または DTO プロジェクションを使用する @Query("SELECT new OrderSummary(o.id, c.name) FROM Order o JOIN o.customer c") List<OrderS
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Spring Data JPA Implementation
Critical Rules
NEVER create repositories for every entity. ALWAYS create repositories only for aggregate roots.
NEVER use complex query method names. ALWAYS use @Query for non-trivial queries.
NEVER use save() blindly. ALWAYS understand persist vs merge semantics (see Vlad Mihalcea's guidance).
Step 1: Identify Repository Needs
Ask:
- Is this an aggregate root? - Only aggregate roots get repositories
- Query complexity? - Simple lookup or complex filtering?
- Read vs Write? - Commands (write) or queries (read)?
- Performance critical? - Large datasets, pagination, or projections?
Step 2: Choose Pattern
| Pattern | When | Read |
|---|---|---|
| Simple Repository | Basic CRUD, 1-2 custom queries | - |
| @Query Repository | Multiple filters, joins, sorting | references/query-patterns.md |
| DTO Projection | Read-only, performance-critical | references/dto-projections.md |
| Custom Repository | Complex logic, bulk ops, Criteria API | references/custom-repositories.md |
| CQRS Query Service | Separate read/write, multiple projections | references/cqrs-query-service.md |
Decision criteria:
| Need | Simple | @Query | DTO | Custom | CQRS |
|---|---|---|---|---|---|
| Basic CRUD | ✅ | ✅ | ❌ | ✅ | ✅ |
| Custom Queries | ❌ | ✅ | ✅ | ✅ | ✅ |
| Best Performance | ✅ | ✅ | ✅✅ | ✅✅ | ✅✅ |
| Complex Logic | ❌ | ❌ | ❌ | ✅ | ✅ |
| Read/Write Separation | ❌ | ❌ | ✅ | ✅ | ✅✅ |
Step 3: Implement Repository
Simple Repository
For basic lookups (1-2 properties):
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
Optional<ProductEntity> findByCode(String code);
List<ProductEntity> findByStatus(ProductStatus status);
}
Asset: Use existing entity and repository patterns
@Query Repository
For 3+ filters, joins, or readability. Read: references/query-patterns.md
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
@Query("""
SELECT DISTINCT o
FROM OrderEntity o
LEFT JOIN FETCH o.items
WHERE o.userId = :userId
ORDER BY o.createdAt DESC
""")
List<OrderEntity> findUserOrders(@Param("userId") Long userId);
}
Asset: assets/query-repository.java - Complete template with examples
DTO Projections
For read-only, performance-critical queries. Read: references/dto-projections.md
public record ProductSummary(Long id, String name, BigDecimal price) {}
@Query("""
SELECT new com.example.ProductSummary(p.id, p.name, p.price)
FROM ProductEntity p
WHERE p.status = 'ACTIVE'
""")
List<ProductSummary> findActiveSummaries();
Asset: assets/dto-projection.java - Records, interfaces, native queries
Custom Repository
For Criteria API, bulk ops. Read: references/custom-repositories.md
// 1. Custom interface
public interface ProductRepositoryCustom {
List<ProductEntity> findByDynamicCriteria(SearchCriteria criteria);
}
// 2. Implementation (must be named <Repository>Impl)
@Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
// Implementation using Criteria API
}
// 3. Main repository extends both
public interface ProductRepository extends JpaRepository<ProductEntity, Long>,
ProductRepositoryCustom {
Optional<ProductEntity> findBySku(String sku);
}
Asset: assets/custom-repository.java - Complete pattern
CQRS Query Service
For Tomato/DDD architectures. Read: references/cqrs-query-service.md
// Repository (package-private) - writes only
interface ProductRepository extends JpaRepository<ProductEntity, ProductId> {
Optional<ProductEntity> findBySku(ProductSKU sku);
}
// QueryService (public) - reads only
@Service
@Transactional(readOnly = true)
public class ProductQueryService {
private final JdbcTemplate jdbcTemplate;
public List<ProductVM> findAllActive() {
return jdbcTemplate.query("""
SELECT id, name, price FROM products
WHERE status = 'ACTIVE'
""",
(rs, rowNum) -> new ProductVM(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")
)
);
}
}
Asset: assets/query-service.java - Full CQRS pattern with JdbcTemplate
Step 4: Entity Relationships
Read: references/relationships.md for detailed guidance
Quick patterns:
// ✅ GOOD: @ManyToOne (most common)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
// ✅ ALTERNATIVE: Just use ID (loose coupling)
@Column(name = "product_id", nullable = false)
private Long productId;
// ❌ AVOID: @OneToMany (query from many side instead)
// Instead: List<OrderItem> items = itemRepository.findByOrderId(orderId);
// ❌ NEVER: @ManyToMany (create join entity instead)
@Entity
public class Enrollment {
@ManyToOne private Student student;
@ManyToOne private Course course;
private LocalDate enrolledAt;
}
Asset: assets/relationship-patterns.java - All relationship types with examples
Step 5: Performance Optimization
Read: references/performance-guide.md for complete checklist
Critical optimizations:
-
Prevent N+1 queries:
// Use JOIN FETCH @Query("SELECT o FROM Order o JOIN FETCH o.customer") List<Order> findWithCustomer(); // Or use DTO projection @Query("SELECT new OrderSummary(o.id, c.name) FROM Order o JOIN o.customer c") List<OrderSummary> findSummaries(); -
Use pagination:
Pageable pageable = PageRequest.of(0, 20); Page<Product> page = repository.findByCategory("Electronics", pageable); -
Mark read services as readOnly:
@Service @Transactional(readOnly = true) public class ProductQueryService { } -
Configure batch size:
spring.jpa.properties.hibernate.jdbc.batch_size: 25
Step 6: Transaction Management
Best practices:
@Service
@Transactional(readOnly = true) // Class-level for read services
public class ProductService {
public List<ProductVM> findAll() {
// Read operations
}
@Transactional // Override for writes
public void createProduct(CreateProductCmd cmd) {
ProductEntity product = ProductEntity.create(cmd);
repository.save(product);
}
}
Rules:
- Use
@Transactional(readOnly = true)at class level for query services - Put
@Transactionalat service layer, not repository - Override with
@Transactionalfor write methods in read services
Step 7: Testing
@DataJpaTest
@Testcontainers
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired
private ProductRepository repository;
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void shouldFindProductByCode() {
ProductEntity product = createTestProduct("P001");
repository.save(product);
Optional<ProductEntity> found = repository.findByCode("P001");
assertThat(found).isPresent();
}
}
Anti-Patterns
| Don't | Do | Why |
|---|---|---|
| Repository for every entity | Only for aggregate roots | Maintains boundaries |
| Use save() blindly | Understand persist/merge | Avoids unnecessary SELECT |
| Long query method names | Use @Query | Readability |
| findAll() without pagination | Use Page<> or Stream | Memory issues |
| Fetch entities for read views | Use DTO projections | Performance |
| FetchType.EAGER | LAZY + JOIN FETCH | Avoids N+1 |
| @ManyToMany | Use join entity | Allows relationship attributes |
| @Transactional in repository | Put in service layer | Proper boundaries |
| Return entities from controllers | Return DTOs/VMs | Prevents lazy issues |
Common Pitfalls
1. LazyInitializationException
Problem: Accessing lazy associations outside transaction
Solution: Use DTO projection or JOIN FETCH
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Optional<Order> findByIdWithItems(@Param("id") Long id);
2. N+1 Queries
Problem: Loading associations in loop
Solution: See references/performance-guide.md
3. Cartesian Product
Problem: Multiple JOIN FETCH with collections
Solution: Separate queries or DTO projections
Quick Reference
When to Load References
- Multiple filters/joins needed →
references/query-patterns.md - Read-only, performance-critical →
references/dto-projections.md - Dynamic queries, bulk operations →
references/custom-repositories.md - CQRS, read/write separation →
references/cqrs-query-service.md - Entity associations →
references/relationships.md - Slow queries, N+1 issues →
references/performance-guide.md
Available Assets
All templates in assets/:
query-repository.java- @Query examples, pagination, bulk opsdto-projection.java- Records, interfaces, native queriescustom-repository.java- Criteria API, EntityManagerquery-service.java- CQRS with JdbcTemplaterelationship-patterns.java- All JPA associations
References
Incorporates best practices from:
- Vlad Mihalcea - Best Spring Data JpaRepository
- Vlad Mihalcea - Spring Data JPA DTO Projections
- Vlad Mihalcea - Spring Data Query Methods
- Vlad Mihalcea - Spring Transaction Best Practices
- Vlad Mihalcea - ManyToOne Best Practices
Browse vladmihalcea.com/blog for deep-dive articles.