ChatGPTが作成したSpring Data JPAの練習問題に挑戦してみる

## はじめに

Spring Data JPAの**カスタムクエリ**について学習したかったのですが、手頃な練習問題を見つけられず…
そこで、**ChatGPTに練習問題を作成してもらうことにしました**。また、作成したコードを答え合わせしてもらいました。

3つの練習問題と、それに取り組んだ結果を紹介します。

## 1．ChatGPTに問題を作成してもらう

プロンプトに以下を入力しました。

```
# 命令書:
あなたは{上級レベルのJavaエンジニア}です。
以下の制約条件をもとに{プログラミングの練習問題}を出力してください。

# 制約条件:
- 私はJavaやSpringフレームワークの基礎知識はあります。
- 私はSpringDataJPAの学習をしています。
- Spring Data JPAを使ってカスタムクエリを作成する練習がしたいです。
- 複雑なクエリの実装に挑戦したいです。
```

以下、作成された問題です。

>### Spring Data JPA カスタムクエリの練習問題
>
>あなたは、図書館の本の貸出と返却の情報を管理するSpring Data JPAプロジェクトを開発しています。図書館の本の情報は以下のようなエンティティクラスで表されています。
>
>```java
>@Entity
>public class Book {
>    @Id
>    @GeneratedValue(strategy = GenerationType.IDENTITY)
>    private Long id;
>
>    private String title;
>
>    private String author;
>
>    private boolean available;
>
>    // コンストラクタ、ゲッター、セッターなどが省略されています
>}
>```
>
>あなたの課題は、以下の複雑なクエリをSpring Data JPAのリポジトリに実装することです。
>
>1. **特定の著者が書いた利用可能な本のリストを取得するメソッド:**
>    - パラメータとして著者の名前が与えられた場合、その著者が書いたかつ利用可能な本のリストを取得するクエリを実装してください。
>2. **指定されたタイトルの本が貸し出し中かどうかを確認するメソッド:**
>    - パラメータとして本のタイトルが与えられた場合、その本が貸し出し中かどうかを確認するクエリを実装してください。
>3. **貸し出し中の本の冊数を取得するメソッド:**
>    - 利用可能でない（貸し出し中の）本の冊数を取得するクエリを実装してください。
>
>**注意:**
>
>- カスタムクエリを実装する際には、JPQLやネイティブクエリを使用してください。
>- クエリ実装後に、それをテストするための適切なテストケースも追加してください。
>- クエリの実装において、できるだけシンプルかつ効率的な方法を検討してください。


## 2．問題を解いてみる

Spring Data JPAにおけるカスタムクエリの実装方法はいくつかあります。
今回は、複数の手法に触れてみたいので、問題毎に異なる手法で実装してみることにします。

### 準備

Springスタータープロジェクトに、以下の依存関係を追加しています。

- Spring Boot DevTools
- Spring Data JPA
- Lombok
- PostgreSQL

以下のクラス・インタフェース等を作成しました。

- エンティティ：Book.java
- エンティティリポジトリ：BookRepository.java
- カスタムリポジトリ：BookRepositoryCustom.java
- カスタムリポジトリ実装クラス：BookRepositoryCustomImpl.java

### **問題①：特定の著者が書いた利用可能な本のリストを取得するメソッド**

> パラメータとして著者の名前が与えられた場合、その著者が書いたかつ利用可能な本のリストを取得するクエリを実装してください。
> 

- 実装方法：**命名規則に従ったメソッド名を使い、クエリを自動実装する**

    ```BookRepository.java
        public List<Book> findByAuthorAndAvailableTrue(String author);
    ```
    

### 問題②：**指定されたタイトルの本が貸し出し中かどうかを確認するメソッド**

> パラメータとして本のタイトルが与えられた場合、その本が貸し出し中かどうかを確認するクエリを実装してください。
> 
- 貸し出し可能：真／貸し出し不可：偽　を返却するメソッドを作成します。
- 実装方法：**`@Query`アノテーションでJPQLクエリーを指定する**
    
    ```BookRepository.java
        @Query("select count(b) = 0 from Book b where b.title = :title and b.available = false")
        public boolean isTitleAvailable(@Param("title") String title);
    ```
    

### 問題③：**貸し出し中の本の冊数を取得するメソッド**

> 利用可能でない（貸し出し中の）本の冊数を取得するクエリを実装してください。
> 
- 実装方法：**カスタムリポジトリで`EntityManager`を使ってクエリーを実装する**
    
    ```BookRepositoryCustomImpl.java
        @Override
    	public long countUnavailableBooks() {
    		return (long) entityManager
    				.createQuery("select count(b) from Book b where b.available = false")
    				.getSingleResult();
    	}
    ```
    

## 3．コードを実装しテストしてみる

各問題に対応する簡単なテストコードを作成し、テストをしてみます。

以下の2つのテストクラスを作成しました。

<details><summary>問題①・②のテスト（BookRepositoryTest）</summary>

```BookRepositoryTest.java
    @SpringBootTest
    public class BookRepositoryTest {
    	
    	@Autowired
    	BookRepository repository;
    	
    	// ①特定の著者が書いた利用可能な本のリストを取得するメソッド
    	@Test
    	@Transactional
    	public void testFindByAuthorAndAvailableTrue() {
    		// 準備
        Book book1 = new Book("Java Programming", "John Doe", true);
        Book book2 = new Book("Spring in Action", "Jane Smith", true);
        Book book3 = new Book("Database Design", "John Doe", false);
        repository.save(book1);
        repository.save(book2);
        repository.save(book3);
    		// 実行
    		List<Book> result = repository.findByAuthorAndAvailableTrue("John Doe");
    		// 検証
    		assertThat(result).hasSize(1);
    		assertThat(result.get(0).getTitle()).isEqualTo("Java Programming");
    	}
    	
    	// ②指定されたタイトルの本が貸し出し中かどうかを確認するメソッド
    	// - 貸し出し可能（true）
    	@Test
    	@Transactional
    	public void testIsTitleAvailable() {
    		// 準備
        Book book1 = new Book("Java Programming", "John Doe", true);
        repository.save(book1);
    		// 実行
    		boolean result = repository.isTitleAvailable("Java Programming");
    		// 検証
    		assertThat(result).isTrue();
    	}
    }
```
    
</details>

<details><summary>問題③のテスト（BookRepositoryCustomTest）</summary>
    
```BookRepositoryCustomTest.java
    @SpringBootTest
    public class BookRepositoryCustomTest {
    
    	@Autowired
    	BookRepository repository;
    
    	// ③貸し出し中の本の冊数を取得するメソッド
    	@Test
    	@Transactional
    	public void testCountUnavailableBooks() {
    		// 準備
    		Book book1 = new Book("Java Programming", "John Doe", true);
    		Book book2 = new Book("Spring in Action", "Jane Smith", false);
    		Book book3 = new Book("Database Design", "John Doe", false);
    		repository.save(book1);
    		repository.save(book2);
    		repository.save(book3);
    		// 実行
    		long result = repository.countUnavailableBooks();
    		// 検証
    		assertThat(result).isEqualTo(2);
    	}
    }
```

</details>

### **テスト結果**

正常終了しました。（JpaDemoApplicationTests.javaは自動生成されたテストクラスです。）

![テスト結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3654286/d5d88380-9209-abc4-8866-a30358a73d29.png "テスト結果")


## 4．ChatGPTに採点してもらう

作成したコードを、ChatGPTに答え合わせしてもらうことにします。

<details><summary>ChatGPTの答え合わせ</summary>

>    あなたの回答に対する答え合わせを行います。
>    
>    ### 問題1: 特定の著者が書いた利用可能な本のリストを取得するメソッド
>    
>    ```java
>    public List<Book> findByAuthorAndAvailableTrue(String author);
>    ```
>    
>    **答え合わせ:**
>    正解です！メソッド名が命名規則に従っており、著者と利用可能な本を検索するためのクエリが適切に生成されます。
>    
>    ### 問題2: 指定されたタイトルの本が貸し出し中かどうかを確認するメソッド
>    
>    ```java
>    @Query("select count(b) = 0 from Book b where b.title = :title and b.available = false")
>    public boolean isTitleAvailable(@Param("title") String title);
>    ```
>    
>    **答え合わせ:**
>    正解です！`@Query` アノテーションを使用してJPQLを指定し、指定されたタイトルの本が貸し出し中でないかを確認するクエリが適切に実装されています。
>    
>    ### 問題3: 貸し出し中の本の冊数を取得するメソッド
>    
>    ```java
>    @Repository
>    public class BookRepositoryCustomImpl implements BookRepositoryCustom {
>
>        @Autowired
>        EntityManager entityManager;
>    
>        @Override
>        public long countUnavailableBooks() {
>            return (long) entityManager
>                    .createQuery("select count(b) from Book b where b.available = false")
>                    .getSingleResult();
>        }
>    }
>    
>    ```
>    
>    **答え合わせ:**
>    正解です！カスタムリポジトリ実装クラスで、貸し出し中の本の冊数を取得するメソッドが適切に実装されています。`EntityManager` を使用してJPQLを実行していますね。
>    
>    素晴らしい回答です！制約条件に従い、問題を解決するためのカスタムリポジトリ実装が適切に行われています。他にも質問があればどうぞ。
>

</details>
    

特に問題はなかったようです。

## おわりに

ChatGPTにカスタムクエリの練習問題を作成してもらい、それに挑戦しました。また、作成したコードの答え合わせをしてもらいました。
練習問題があると学習がはかどるので、非常にありがたかったです。

今後は、**コーディング規約に基づいたコードのレビュー**も行ってもらおうと考えています。

## 参考文献

- [JPA クエリメソッド :: Spring Data JPA - リファレンス (pleiades.io)](https://spring.pleiades.io/spring-data/jpa/reference/jpa/query-methods.html)
- [Spring Data JPA でのクエリー実装方法まとめ #Java - Qiita](https://qiita.com/tag1216/items/55742fdb442e5617f727#spring-data-jpa-%E3%81%AE%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%BC%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95)