[速習] Spring 第5回 ～エンタープライズアプリケーションの実装パターン～ (Spring Data JPAの基礎とリポジトリパターン)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3521573/cbb3b5d9-e19a-4f11-9a5b-d105d9b29935.png)

https://qiita.com/ShigemoriMasato/items/02d5e803a6815e04c7ea

前回はSpring MVCを使った実践的なWebアプリケーション構築について解説しました。今回は、エンタープライズアプリケーション[^1]に不可欠な**データアクセス層の実装**について、Spring Data JPAの基礎を中心に解説します。

## Spring Data JPAとは

Spring Data JPAは、JPA[^2]ベースのリポジトリ層[^3]を簡単に実装するためのフレームワークです。ボイラープレートコード[^4]を大幅に削減し、データアクセス層の実装を効率化します。

データベースを使うアプリケーションでは、データの保存や取得のために多くのコードを書く必要があります。Spring Data JPAは、これらの定型的なコードを自動的に生成してくれるため、開発者は「商品を保存する」「IDで商品を探す」といった基本的な操作のコードを自分で書く必要がなくなります。

```mermaid
graph LR
    Controller["@Controller<br/>リクエスト処理<br/>━━━━━━━━━━<br/>Webブラウザからの<br/>リクエストを受け取る窓口"]
    Service["@Service<br/>ビジネスロジック<br/>トランザクション境界<br/>━━━━━━━━━━<br/>実際の処理を行う場所<br/>例：在庫チェック、注文処理"]
    Repository["Repository Interface<br/>━━━━━━━━━━<br/>インターフェース定義のみ<br/>findByName等のメソッド定義<br/>━━━━━━━━━━<br/>データベース操作の<br/>指示書を書く場所"]
    SpringDataJPA["Spring Data JPA<br/>━━━━━━━━━━<br/>実装の自動生成<br/>メソッド名からクエリ生成<br/>ページネーション処理<br/>━━━━━━━━━━<br/>指示書を実際の<br/>SQL文に変換する翻訳機"]
    JPA["JPA Provider (Hibernate)<br/>━━━━━━━━━━<br/>O/Rマッピング<br/>@Queryの実行<br/>━━━━━━━━━━<br/>JavaオブジェクトとDBの<br/>データを相互変換"]
    DB[("Relational Database<br/>MySQL/PostgreSQL等<br/>━━━━━━━━━━<br/>データを実際に<br/>保管する倉庫")]
    
    Controller --> Service
    Service --> Repository
    Repository --> SpringDataJPA
    SpringDataJPA --> JPA
    JPA --> DB
    DB --> JPA
    JPA --> SpringDataJPA
    SpringDataJPA --> Repository
    Repository --> Service
    Service --> Controller
    
    style Controller fill:#ff9999,stroke:#333,stroke-width:2px
    style Service fill:#99ccff,stroke:#333,stroke-width:2px
    style Repository fill:#99ff99,stroke:#333,stroke-width:2px
    style SpringDataJPA fill:#ffffcc,stroke:#333,stroke-width:3px
    style JPA fill:#99ff99,stroke:#333,stroke-width:2px
    style DB fill:#ffcc99,stroke:#333,stroke-width:2px
```

### 主な特徴

- **リポジトリの自動実装**: インターフェースを定義するだけで基本的なCRUD[^5]操作が可能
- **メソッド名からクエリを自動生成**: `findByName`のようなメソッド名からSQL[^6]を生成
- **ページネーション[^7]・ソート機能**: 大量データの効率的な処理
- **カスタムクエリのサポート**: 複雑なクエリも`@Query`アノテーションで定義可能

## プロジェクトのセットアップ

まず、Spring Data JPAを使うために必要な依存関係を追加します。

```xml
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- MySQL Driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- H2 Database (開発・テスト用) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!-- Spring Boot Starter Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
```

### アプリケーション設定

```yaml
# application.yml
spring:
  datasource:
    # 開発環境ではH2インメモリデータベースを使用
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password: 
    
  jpa:
    # Hibernateの設定
    hibernate:
      ddl-auto: create-drop  # 起動時にテーブルを作成、終了時に削除
    show-sql: true  # 実行されるSQLをコンソールに表示
    properties:
      hibernate:
        format_sql: true  # SQLを見やすく整形
        
  h2:
    console:
      enabled: true  # H2コンソールを有効化（http://localhost:8080/h2-console）
```

## エンティティクラスの設計

JPAエンティティ[^8]は、データベースのテーブルとマッピングされるJavaクラスです。実際の商品管理システムを例に、詳しく見ていきましょう。

エンティティクラスは、データベースのテーブルに保存するデータの設計図のようなものです。例えば「商品」テーブルには商品名、価格、在庫数などの情報が必要ですが、これらをJavaのクラスとして定義します。

```java
package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity  // このクラスがJPAエンティティであることを示す
@Table(name = "products")  // 対応するテーブル名を指定
@EntityListeners(AuditingEntityListener.class)  // 監査機能を有効化
@Getter  // Lombokでgetterメソッドを自動生成
@Setter  // Lombokでsetterメソッドを自動生成
public class Product {
    
    @Id  // 主キー（各商品を一意に識別するID）であることを示す
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // DBの自動採番を使用
    private Long id;
    
    @Column(nullable = false, length = 100)  // NOT NULL制約、最大100文字
    private String name;  // 商品名
    
    @Column(nullable = false)  // NOT NULL制約（必須項目）
    private Integer price;  // 価格
    
    @Column(length = 500)  // 最大500文字（NULLは許可）
    private String description;  // 商品説明（任意項目）
    
    @Column(nullable = false)
    private Integer stockQuantity = 0;  // 在庫数（デフォルト値を0に設定）
    
    // 楽観的ロック用のバージョン番号
    // 更新時に自動的にインクリメントされ、同時更新を検出
    // 例：AさんとBさんが同時に在庫を更新しようとした時の衝突を防ぐ
    @Version
    private Long version;
    
    // 作成日時を自動記録（監査機能）
    // いつこの商品が登録されたかを自動的に記録
    @CreatedDate
    @Column(nullable = false, updatable = false)  // 更新不可
    private LocalDateTime createdAt;
    
    // 更新日時を自動記録（監査機能）
    // いつこの商品情報が最後に更新されたかを自動的に記録
    @LastModifiedDate
    private LocalDateTime updatedAt;
    
    // カテゴリとの多対1の関係
    // 複数の商品が1つのカテゴリに属する（例：「書籍」カテゴリに複数の本）
    // FetchType.LAZY: 必要になるまでカテゴリをロードしない（パフォーマンス対策）
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")  // 外部キーカラム名
    private Category category;
    
    // ビジネスメソッドの例：在庫を減らす
    // エンティティに業務ロジックを含めることで、データの整合性を保つ
    public void decrementStock(int quantity) {
        if (this.stockQuantity < quantity) {
            throw new IllegalArgumentException("在庫が不足しています");
        }
        this.stockQuantity -= quantity;
    }
    
    // 在庫を増やす
    public void incrementStock(int quantity) {
        this.stockQuantity += quantity;
    }
}
```

### カテゴリエンティティ

```java
package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "categories")
@Getter
@Setter
public class Category {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true, length = 50)
    private String name;  // カテゴリ名（重複不可）
    
    @Column(length = 200)
    private String description;  // カテゴリの説明
    
    // 商品との1対多の関係
    // 1つのカテゴリに複数の商品が属する
    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
    private List<Product> products = new ArrayList<>();
}
```

### エンティティの関連マッピング

データベースでは、テーブル同士が関連を持つことがよくあります。例えば、商品はカテゴリに属し、注文には複数の商品が含まれます。このような関係をER図で表すと以下のようになります。

```mermaid
erDiagram
    PRODUCTS ||--o{ ORDER_ITEMS : "has many"
    CATEGORIES ||--o{ PRODUCTS : "contains"
    ORDERS ||--o{ ORDER_ITEMS : "has many"
    
    PRODUCTS {
        bigint id PK "商品ID（主キー）"
        varchar name "商品名"
        int price "価格"
        varchar description "商品説明"
        int stock_quantity "在庫数"
        bigint version "バージョン番号"
        bigint category_id FK "カテゴリID（外部キー）"
        timestamp created_at "作成日時"
        timestamp updated_at "更新日時"
    }
    
    CATEGORIES {
        bigint id PK "カテゴリID"
        varchar name "カテゴリ名"
        varchar description "カテゴリ説明"
    }
```

### エンティティ設計のポイント

1. **`@Version`による楽観的ロック[^9]**: 同時更新による不整合を防ぐ仕組み。例えば、2人が同時に在庫を更新しようとした時、後から更新した人にエラーを返して、データの不整合を防ぎます。

2. **監査機能[^10]の活用**: `@CreatedDate`、`@LastModifiedDate`で自動的に日時を記録。「いつ誰が作成・更新したか」という履歴を自動で残せます。

3. **適切なフェッチ戦略[^11]**: `FetchType.LAZY`で遅延ロード。必要な時だけ関連データを取得することで、パフォーマンスを向上させます。

4. **Lombok[^13]の活用**: `@Getter`や`@Setter`で、getterやsetterメソッドを自動生成。手作業でこれらのメソッドを書く必要がなくなります。

### 監査機能を有効にする設定

```java
package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing  // JPA監査機能を有効化
public class JpaConfig {
    // Spring Bootが自動的に設定を行うため、通常は追加の設定は不要
    // この設定により、@CreatedDateや@LastModifiedDateが自動的に動作する
}
```

## リポジトリインターフェースの実装

Spring Data JPAでは、`JpaRepository`を継承するだけで基本的なCRUD操作が可能になります。実装クラスは実行時に自動生成されます。

リポジトリは、データベースとのやり取りを担当する部分です。商品を倉庫（データベース）から取り出したり、新しい商品を倉庫に保管したりする「倉庫係」のような役割を持ちます。

### 基本的なリポジトリインターフェース

```java
package com.example.demo.repository;

import com.example.demo.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    // JpaRepositoryを継承することで、以下のメソッドが自動的に利用可能:
    
    // ===== 基本的なCRUD操作（自動で提供される） =====
    // save(Product entity) - データの保存・更新
    // findById(Long id) - IDでデータを検索
    // findAll() - 全データを取得
    // deleteById(Long id) - IDでデータを削除
    // count() - データ件数を取得
    // existsById(Long id) - IDのデータが存在するか確認
    
    // ===== カスタムクエリメソッド（メソッド名から自動生成） =====
    
    // 商品名で検索
    // SQLが自動生成される: SELECT * FROM products WHERE name = ?
    Optional<Product> findByName(String name);
    
    // 商品名に特定の文字列を含む商品を検索
    // 例：findByNameContaining("パン") → "パン"を含む商品を検索
    // SQL: SELECT * FROM products WHERE name LIKE '%パン%'
    List<Product> findByNameContaining(String keyword);
    
    // 価格範囲で検索
    // SQL: SELECT * FROM products WHERE price >= ? AND price <= ?
    List<Product> findByPriceBetween(Integer minPrice, Integer maxPrice);
    
    // 在庫がある商品を価格の安い順で取得
    // SQL: SELECT * FROM products WHERE stock_quantity > 0 ORDER BY price ASC
    List<Product> findByStockQuantityGreaterThanOrderByPriceAsc(Integer stockQuantity);
    
    // カテゴリIDで商品を検索
    // SQL: SELECT * FROM products WHERE category_id = ?
    List<Product> findByCategoryId(Long categoryId);
}
```

### カテゴリリポジトリ

```java
package com.example.demo.repository;

import com.example.demo.entity.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
    // カテゴリ名で検索（一意制約があるため、Optionalで返す）
    Optional<Category> findByName(String name);
}
```

### クエリメソッドの命名規則

Spring Data JPAでは、メソッド名から自動的にSQLを生成します。この命名規則を理解すると、様々な検索条件を簡単に実装できます。

```mermaid
graph LR
    subgraph "メソッド名の構成"
        direction LR
        A[find/read/get/query/search/stream] -->|"返却型"| B[By]
        B -->|"条件"| C[プロパティ名]
        C -->|"演算子"| D[And/Or]
        D -->|"追加条件"| E[OrderBy]
    end
```

### よく使うクエリメソッドのパターン

```java
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    // ===== 基本的な検索 =====
    List<Product> findByName(String name);  // 完全一致
    List<Product> findByNameIgnoreCase(String name);  // 大文字小文字を無視
    
    // ===== 部分一致検索 =====
    List<Product> findByNameContaining(String keyword);  // 部分一致
    List<Product> findByNameStartingWith(String prefix);  // 前方一致
    List<Product> findByNameEndingWith(String suffix);  // 後方一致
    
    // ===== 比較演算子 =====
    List<Product> findByPriceLessThan(Integer price);  // より小さい
    List<Product> findByPriceLessThanEqual(Integer price);  // 以下
    List<Product> findByPriceGreaterThan(Integer price);  // より大きい
    List<Product> findByPriceGreaterThanEqual(Integer price);  // 以上
    
    // ===== 複数条件 =====
    List<Product> findByNameAndPrice(String name, Integer price);  // AND条件
    List<Product> findByNameOrPrice(String name, Integer price);  // OR条件
    
    // ===== NULL チェック =====
    List<Product> findByDescriptionIsNull();  // NULLのもの
    List<Product> findByDescriptionIsNotNull();  // NULLでないもの
    
    // ===== IN句 =====
    List<Product> findByNameIn(List<String> names);  // 複数の値のいずれか
    
    // ===== ソート =====
    List<Product> findByOrderByPriceAsc();  // 価格の昇順
    List<Product> findByOrderByPriceDesc();  // 価格の降順
    
    // ===== 複雑な条件 =====
    List<Product> findByNameContainingAndPriceLessThanOrderByPriceDesc(
        String keyword, Integer maxPrice
    );  // 名前に特定文字を含み、価格が指定値未満の商品を価格の高い順で取得
}
```

## サービス層の実装

リポジトリを使用して、ビジネスロジックを実装するサービス層を作成します。

```java
package com.example.demo.service;

import com.example.demo.entity.Product;
import com.example.demo.entity.Category;
import com.example.demo.repository.ProductRepository;
import com.example.demo.repository.CategoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor  // finalフィールドのコンストラクタを自動生成
public class ProductService {
    
    private final ProductRepository productRepository;
    private final CategoryRepository categoryRepository;
    
    // 商品を登録する
    @Transactional
    public Product createProduct(String name, Integer price, 
                               String description, Long categoryId) {
        // カテゴリの存在確認
        Category category = categoryRepository.findById(categoryId)
            .orElseThrow(() -> new IllegalArgumentException(
                "カテゴリが見つかりません: " + categoryId));
        
        // 商品名の重複チェック
        productRepository.findByName(name).ifPresent(existing -> {
            throw new IllegalArgumentException(
                "同名の商品が既に存在します: " + name);
        });
        
        // 新しい商品を作成
        Product product = new Product();
        product.setName(name);
        product.setPrice(price);
        product.setDescription(description);
        product.setCategory(category);
        product.setStockQuantity(0);  // 初期在庫は0
        
        // データベースに保存
        return productRepository.save(product);
    }
    
    // 全商品を取得
    @Transactional(readOnly = true)  // 読み取り専用トランザクション
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }
    
    // 商品をID検索
    @Transactional(readOnly = true)
    public Product getProductById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException(
                "商品が見つかりません: " + id));
    }
    
    // 商品名で検索
    @Transactional(readOnly = true)
    public List<Product> searchProductsByName(String keyword) {
        return productRepository.findByNameContaining(keyword);
    }
    
    // 価格範囲で検索
    @Transactional(readOnly = true)
    public List<Product> searchProductsByPriceRange(Integer minPrice, 
                                                   Integer maxPrice) {
        return productRepository.findByPriceBetween(minPrice, maxPrice);
    }
    
    // 在庫を更新
    @Transactional
    public Product updateStock(Long productId, Integer quantity) {
        Product product = getProductById(productId);
        
        if (quantity > 0) {
            product.incrementStock(quantity);
        } else {
            product.decrementStock(-quantity);
        }
        
        return productRepository.save(product);
    }
    
    // 商品を削除
    @Transactional
    public void deleteProduct(Long productId) {
        // 存在確認
        if (!productRepository.existsById(productId)) {
            throw new IllegalArgumentException(
                "商品が見つかりません: " + productId);
        }
        
        productRepository.deleteById(productId);
    }
}
```

## コントローラの実装

RESTful APIとして商品管理機能を公開します。

```java
package com.example.demo.controller;

import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
    
    private final ProductService productService;
    
    // 商品作成のリクエストDTO
    @lombok.Data
    public static class CreateProductRequest {
        private String name;
        private Integer price;
        private String description;
        private Long categoryId;
    }
    
    // 在庫更新のリクエストDTO
    @lombok.Data
    public static class UpdateStockRequest {
        private Integer quantity;
    }
    
    // 商品を作成
    @PostMapping
    public ResponseEntity<Product> createProduct(
            @RequestBody CreateProductRequest request) {
        Product product = productService.createProduct(
            request.getName(),
            request.getPrice(),
            request.getDescription(),
            request.getCategoryId()
        );
        return ResponseEntity.status(HttpStatus.CREATED).body(product);
    }
    
    // 全商品を取得
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }
    
    // 商品をID検索
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        return productService.getProductById(id);
    }
    
    // 商品名で検索
    @GetMapping("/search")
    public List<Product> searchProducts(@RequestParam String keyword) {
        return productService.searchProductsByName(keyword);
    }
    
    // 価格範囲で検索
    @GetMapping("/search/price")
    public List<Product> searchProductsByPrice(
            @RequestParam Integer minPrice,
            @RequestParam Integer maxPrice) {
        return productService.searchProductsByPriceRange(minPrice, maxPrice);
    }
    
    // 在庫を更新
    @PatchMapping("/{id}/stock")
    public Product updateStock(
            @PathVariable Long id,
            @RequestBody UpdateStockRequest request) {
        return productService.updateStock(id, request.getQuantity());
    }
    
    // 商品を削除
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
    }
}
```

## 動作確認

アプリケーションを起動して、実際に動作を確認してみましょう。

### 初期データの投入

```java
package com.example.demo;

import com.example.demo.entity.Category;
import com.example.demo.repository.CategoryRepository;
import com.example.demo.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {
    
    private final CategoryRepository categoryRepository;
    private final ProductService productService;
    
    @Override
    public void run(String... args) throws Exception {
        // カテゴリを作成
        Category books = new Category();
        books.setName("書籍");
        books.setDescription("技術書、ビジネス書など");
        books = categoryRepository.save(books);
        
        Category electronics = new Category();
        electronics.setName("家電");
        electronics.setDescription("生活家電、PC周辺機器など");
        electronics = categoryRepository.save(electronics);
        
        // 商品を作成
        productService.createProduct(
            "Spring Boot入門", 3000, 
            "Spring Bootの基礎を学ぶ", books.getId()
        );
        
        productService.createProduct(
            "Java実践ガイド", 3500, 
            "Javaの実践的な使い方を解説", books.getId()
        );
        
        productService.createProduct(
            "ワイヤレスマウス", 2500, 
            "Bluetooth対応のマウス", electronics.getId()
        );
    }
}
```

### APIの使用例

```bash
# 全商品を取得
curl http://localhost:8080/api/products

# 商品を作成
curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "キーボード",
    "price": 5000,
    "description": "メカニカルキーボード",
    "categoryId": 2
  }'

# 商品名で検索
curl http://localhost:8080/api/products/search?keyword=Spring

# 在庫を更新（10個追加）
curl -X PATCH http://localhost:8080/api/products/1/stock \
  -H "Content-Type: application/json" \
  -d '{"quantity": 10}'
```

### H2コンソールでデータ確認

ブラウザで `http://localhost:8080/h2-console` にアクセスし、以下の情報でログイン：
- JDBC URL: `jdbc:h2:mem:testdb`
- User Name: `sa`
- Password: (空欄)

SQLを実行してデータを確認できます：
```sql
-- 全商品を表示
SELECT * FROM PRODUCTS;

-- カテゴリ別の商品数
SELECT c.NAME, COUNT(p.ID) 
FROM CATEGORIES c 
LEFT JOIN PRODUCTS p ON c.ID = p.CATEGORY_ID 
GROUP BY c.NAME;
```

## まとめ

今回は、Spring Data JPAの基礎について解説しました。

### 本記事で学んだこと
- **Spring Data JPAの基本概念**: リポジトリパターンによるデータアクセスの抽象化
- **エンティティクラスの設計**: データベーステーブルとJavaクラスのマッピング
- **リポジトリインターフェースの実装**: インターフェースの定義だけでCRUD操作を実現
- **クエリメソッドの命名規則**: メソッド名からSQLを自動生成する仕組み
- **基本的なCRUD操作の実装**: サービス層とコントローラ層の実装

Spring Data JPAを利用することで、データベース操作に関するコードを大幅に削減し、ビジネスロジックの実装により集中できるようになります。


[^1]: **エンタープライズアプリケーション** - 企業の業務システムなど、高い信頼性・拡張性・保守性が求められる大規模なアプリケーション。
[^2]: **JPA (Java Persistence API)** - JavaでO/Rマッピング（オブジェクト関係マッピング）を実現するための標準API。HibernateはJPAの代表的な実装。
[^3]: **リポジトリ層** - データアクセスを担当する層。データの永続化と復元を担当し、ビジネスロジックからデータアクセスの詳細を隠蔽する。
[^4]: **ボイラープレートコード** - 定型的で繰り返し記述が必要なコード。Spring Data JPAはこれらを自動生成することで開発効率を向上させる。
[^5]: **CRUD操作** - Create（作成）、Read（読取）、Update（更新）、Delete（削除）の基本的なデータ操作の総称。
[^6]: **SQL (Structured Query Language)** - リレーショナルデータベースを操作するための標準的なクエリ言語。
[^7]: **ページネーション** - 大量のデータを一定数ずつに分割して表示する仕組み。パフォーマンスとユーザビリティの向上に寄与。
[^8]: **エンティティ** - データベースのテーブルに対応するJavaオブジェクト。`@Entity`アノテーションで定義される。
[^9]: **楽観的ロック** - データ更新時にバージョン番号を確認することで、他のトランザクションによる更新を検出する仕組み。
[^10]: **監査機能** - データの作成日時、更新日時などを自動的に記録する機能。
[^11]: **フェッチ戦略** - 関連エンティティをいつ取得するかを決定する戦略。EAGER（即時取得）とLAZY（遅延取得）がある。
[^13]: **Lombok** - Javaのボイラープレートコードを削減するライブラリ。`@Getter`、`@Setter`などのアノテーションでコードを自動生成。