@GetMappingとパス変数
このレッスンでは、@GetMapping でパスを対応づけ、@PathVariable でパスの一部を引数として受け取れるようになります。
@GetMapping はパスとメソッドの対応づけ
@GetMapping("/users") は「GET メソッドで /users に来たリクエストを、このメソッドで処理する」という宣言です。@RequestMapping(method = RequestMethod.GET, path = "/users") の短縮形で、実務ではほぼ短い方だけを使います。POST / PUT / DELETE / PATCH にもそれぞれ対応するアノテーションがあります。
Java
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public List<UserResponse> list() { ... }
@GetMapping("/{id}")
public UserResponse get(@PathVariable Long id) { ... }
}波かっこがパス変数
/{id} の波かっこは「ここは何が来てもよい、その値を id という名前で覚えておく」という意味です。/users/12 に来れば id は 12、/users/999 に来れば id は 999 になります。この覚えた値をメソッドの引数として受け取るのが @PathVariable です。
Java
@GetMapping("/{id}")
public UserResponse get(@PathVariable Long id) { ... }パスの中の名前と引数名が同じなら、そのまま結びつきます。名前を変えたい場合は @PathVariable("id") Long userId のように、対応させたいパス変数名を明示します。
ここで注目したいのが型変換です。URL の中身は文字列でしかありませんが、引数の型を Long にしておくと Spring が数値へ変換してくれます。数値にできない値、たとえば /users/abc が来た場合は変換に失敗し、既定では 400 が返ります。
複数のパス変数
階層のあるリソースでは、パス変数が 2 つ以上になります。
Java
@GetMapping("/{userId}/orders/{orderId}")
public OrderResponse getOrder(@PathVariable Long userId,
@PathVariable Long orderId) { ... }/users/12/orders/34 というリクエストで userId が 12、orderId が 34 になります。所属関係をパスの階層で表せるのが REST の分かりやすいところです。
パス変数とクエリパラメータの使い分け
| 置き場所 | 書き方 | 何を表すか |
|---|---|---|
| パス変数 | /users/12 | どのリソースかを一意に決めるもの |
| クエリパラメータ | /users?page=2 | 絞り込み・並び順・ページングなど、表現の調整 |
迷ったら「これが変わると別のリソースになるか」と考えます。ID が変われば別の人なのでパスに置きます。ページ番号が変わっても対象の集合は同じなのでクエリに置きます。
見つからなかったときをどうするか
/users/999 のように、形式は正しいけれど該当データが無い場合があります。この場合は 404 を返すのが素直です。Spring では例外を投げて、後のレッスンで扱う例外ハンドラで 404 に変換する、という形が一般的です。
Java
@GetMapping("/{id}")
public UserResponse get(@PathVariable Long id) {
return repository.findById(id)
.map(UserResponse::from)
.orElseThrow(() -> new UserNotFoundException(id));
}大事なのは、存在しないことは異常ではなく想定内という点です。あらかじめ分岐を用意しておかないと、null がそのまま流れて別の場所で落ちます。
演習で確かめます
Spring では @PathVariable が URL から ID を切り出して型変換までやってくれます。ここではその先の部分、つまり「取り出した ID でデータを引き、あればその表現を、無ければ見つからなかったことを返す」という判断を自分で書きます。Map を疑似的なデータ置き場として使ってください。
要件
- 疑似データを Map で用意し、1 は taro、2 は hanako、12 は sato とすること
- 存在する id では id,名前 の形の文字列を返すこと
- 存在しない id では not-found を返すこと
入出力例
findUser(1) → "1,taro"
findUser(12) → "12,sato"
findUser(2) → "2,hanako"
findUser(999) → "not-found"