SpringBoot Controllerの引数 アノテーションをまとめたかった

Controller書くときに「どうやってデータ受け取ればいいんだっけ」と悩むのでまとめておく
いつも既存コードコピペじゃまずいので動作確認

SpringBoot3.3.1 で動作確認

# 前提として知っとくといいこと

「リクエストパラメータ」についておさらい

- GET
    - `GET /book?id=2&title=sampleBook`
    - `?`の後ろに書いていくやつ  
    - 「URLパラメータ」「クエリパラメータ」呼び方が安定してない気がする
    - 基本的に参照型に使う
    - データ長制限あり。長い文字列を扱うときは注意
- POST
    - ```POST /addBook id=2 author=John```
    - パラメータは「リクエストボディ」に記載
        - curlコマンドの`-d`
    - 更新系に使う
    - 文字数制限なし
    - Jsonとの相性がいい
- PUT,DELETE,その他もろもろは無視


実際のところ
- aタグ
    - GET
- formタグによるsubmit
    - GET,POST選べる
    - POSTの場合は`Content-Type:application/x-www-form-urlencoded` →`@RequestBody`では受け取れない
- JSのfetch関数
    - GET,POST選べるけど
    - jsonと連携できるこれが強い
```javascript
fetch("/submit", {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ username: "testuser", age: 25 })
});
```
- ちょっと古いがJqueryのajax関数
    - GET,POST選べる
    - POST,`data`を使う場合は`Contnet-Type:application/x-www-form-urlencoded`となる
```js
$.ajax({
  url: "/api/data",
  method: "POST",
  data: {
    username: "John",
    age: 30
  }
});

```



# 結論

| アノテーション         | GET  | POST | 説明 |
|--------------------|------|------|------|
| `@RequestParam`    | ○   | ○   | クエリパラメータ (`?key=value`) やフォームの `application/x-www-form-urlencoded` を取得 |
| `@PathVariable`    | ○   | ○   | URL のパスパラメータ (`/users/{id}`) を取得 |
| `@RequestBody`     | ×   | ○   | JSON データをリクエストボディから取得 (`application/json`) |
| `@ModelAttribute`  | ○   | ○   | フォームの `application/x-www-form-urlencoded` を DTO にマッピング |

- モダンなAPIはjsonでやりとりする(偏見)ので、`@RequestBody`が柔軟で優秀
   - しかし、GETで使えないというデメリットあり
   - ただやっぱりJson連携は超絶便利なので実は全部POSTで受けるってのもありなのでは？
   - フロントエンドなにもわからん
- GETの場合は随時適切なアノテーションを選択する
- 


# @RequestParam

```java
@GetMapping("/book")
public String getBook(@RequestParam(name = "title", required = false) String title) {
    // クエリパラメータから受け取ったデータを処理します
    return "Book title: " + title;
}
```

URLパラメータを受け取るやつ
curlで叩くには
```
curl -X GET "http://localhost:8080/book?title=SampleBook"
```

`(name = "title")`をつけないと以下エラー
`java.lang.IllegalArgumentException: Name for argument of type [java.lang.String] not specified, and parameter name information not available via reflection. Ensure that the compiler uses the '-parameters flag`


名前を明示的につけなくても問題ないんじゃなかった？と思ったけどSpring Framework 6.1以降は明示しないとダメらしい
https://qiita.com/mitecha/items/a95546652c13cf041f54

`required`はデフォルトtrue


引数の型もいい感じにバインドしてくれる
```java
@GetMapping("/book")
public String getBook(
    @RequestParam(name="title", required = false) String title, 
    @RequestParam(name = "id") int id,
    @RequestParam(name = "available") boolean available,
    @RequestParam(name = "tags") List<String> tags,
    @RequestParam(name = "publishedDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate publishedDate
    ) {
    // クエリパラメータから受け取ったデータを処理します
    return String.format(
        "Book title: %s\nBook ID: %d\nAvailable: %b\nTags: %s\nPublished date: %s",
        title, id, available, String.join(", ", tags), publishedDate
    );
}

curl -X GET "http://localhost:8080/book?title=Sample+Book&id=1&available=true&tags=fiction&tags=adventure&publishedDate=2025-03-18"
```
Listは2回指定
日付型も柔軟に受け入れできる


当然カスタムクラスもいける
```java
@GetMapping("/filter")
public String filterBooks(BookFilter filter) {
    // カスタムクラスから受け取ったデータを処理します
    return "Filtering books by genre: " + filter.getGenre() + ", author: " + filter.getAuthor();
}

//カスタムクラスの定義
public class BookFilter {
    private String genre;
    private String author;

    // ゲッターとセッター
}

curl -X GET "http://localhost:8080/filter?genre=fiction&author=John+Doe"
```
やりたいことはだいたいできる
べんり

# @PathVariable

```java
@GetMapping("/book/{id}")
public String getBookById(@PathVariable(name = "id") int id) {
    // idから本を取得する処理
    return "Book id: " + id;
}
```
```
curl -X GET "http://localhost:8080/book/2"
```

パスパラメータを受け取りたいときに使う
性質上IDなどを受け取りがち

カスタムクラスと両立することもあるけど、基本はIDなどの識別子だけ`@PathVariable`をつけるのが一般的か
```java
@GetMapping("/book/details/{id}")
public String getBookDetails(@PathVariable(name = "id") int id, BookFilter filter) {
    return String.format("Book ID: %d, Genre: %s, Author: %s", id, filter.getGenre(), filter.getAuthor());
}

curl -X GET "http://localhost:8080/book/details/123?genre=fiction&author=JohnDoe"
```
フィルター部分はクエリパラメータで指定する


# @ModelAttribute

基本的に複雑なデータを受け取りたいときに使う
渡したいパラメータが多い！→クラス(Model)にまとめよう→ModelAttribute使おう
GET,POSTは問わない
```java
// GET
@GetMapping("/book/model/details")
public String getBookDetailsByModelGet(@ModelAttribute BookFilter filter) {
    // フィルターから本の詳細を取得する処理
    return String.format("Get! Genre: %s, Author: %s", filter.getGenre(), filter.getAuthor());
}

// POST
@PostMapping("/book/model/details")
public String getBookDetailsByModelPost(@ModelAttribute BookFilter filter) {
    // フィルターから本の詳細を取得する処理
    return String.format("Post! Genre: %s, Author: %s", filter.getGenre(), filter.getAuthor());
}

//GETにリクエスト
curl -X GET "http://localhost:8080/book/model/details?genre=fiction&author=JohnDoe"

//POSTにリクエスト
curl -X POST -d "genre=fiction&author=JohnDoe" "http://localhost:8080/book/model/details"
```


# @RequestBody

APIつくるときは基本これ
jsonを受け取るときはこれでいい
基本的には引数はカスタムクラス1つにピチッとまとめて使いがち
どうせjsonだしまとめたほうがええやんみたいな考え

```java

@PostMapping("/books")
public String createBook(@RequestBody BookRequest bookRequest) {
    // ここで受け取ったデータを処理
    return String.format(
        "Book received: %s\nBook ID: %d\nAvailable: %b\nTags: %s\nPublished date: %s",
        bookRequest.getTitle(), bookRequest.getId(), bookRequest.isAvailable(), String.join(", ", bookRequest.getTags()), bookRequest.getPublishedDate()
    );
}
```
```
curl -X POST http://localhost:8080/addBook -H "Content-Type: application/json" -d '{
    "title": "New Book",
    "available": true,
    "tags": ["fiction", "adventure"],
    "publishedDate": "2025-03-18"
}'
```

と思ったらこういうパターンもあった
`@PathVariable`で変更値対象のidを取得
`@RequestBody`で変更内容データを取得

```java
@PostMapping("/updateBook/{id}")
public String updateBook(@PathVariable(name = "id") int id, @RequestBody BookRequest bookRequest) {
    // ここで受け取ったデータを処理
    return String.format(
        "Book updated: %s\nAvailable: %b\nTags: %s\nPublished date: %s",
        bookRequest.getTitle(), bookRequest.isAvailable(), String.join(", ", bookRequest.getTags()), bookRequest.getPublishedDate()
    );
}
```

```
curl -X POST http://localhost:8080/updateBook/1 -H "Content-Type: application/json" -d '{
    "title": "Updated Book",
    "available": true,
    "tags": ["fiction", "adventure"],
    "publishedDate": "2025-03-18"
}'
```

# 参考

プロになるためのSpring入門――ゼロからの開発力養成講座
［改訂新版］プロになるためのWeb技術入門
Spring徹底入門 第2版 Spring FrameworkによるJavaアプリケーション開発