追記
- Controllerのコードの一部が切れていたので修正
- Controllerのアノテーション周りの話が抜けていたので追記
内容
- Java初心者が夏休み中にSpringの勉強をする
- Spring Frameworkはハードルが高そうだったので一先ずSpringBootから
- お題は初心者がよくやるTodoアプリのREST API
詳しく触れないこと
- gradle周辺
- Database周辺
- JPA
- H2 Database
本編
- 開発は
IntelliJ IDEA
を使用- ビルドとかアプリケーションの起動はお任せ
雛形作成
-
https://start.spring.io/ で作成
- Project:
Gradle Project
- Language:
Java
- Spring Boot:
2.1.8
- Project Metadaba:
- Group:
com.example
- Artifact:
todoapp
- Options: そのまま
- Group:
- Dependencies:
Spring Web
Spring Data JPA
H2 Database
Lombok
- Project:
- ダウンロードされたzipファイルをunzipしてIntelliJでopenしたところから開始
パッケージ構成
-
com.example.todoapp
直下に以下を並べるcontrollers
repositories
models
-
services
とかもあるべきかもしれないけど今回は割愛
コード
models
- Task.java
package com.example.todoapp.models;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Data
@Entity
public class Task {
@Id
@GeneratedValue
private Long id;
private String summary;
private Boolean done = false;
public Task(){}
}
- 各アノテーションについて
-
@Data
- 各フィールドのsetterとgetterを定義してくれる
-
toString()
メソッドやhashCode()
メソッドを定義してくれる
-
@Entity
- エンティティであることを示す。
- このクラスのフィールドを持つテーブルがDBに作られる
-
@Id
- エンティティの主キーのフィールドを指定する
-
@GeneratedValue
- 主キーの値の生成方法を指定する
-
strategy
やgenerator
を指定して挙動を変えられそう(試してない)
-
repositories
- TaskRepository.java
package com.example.todoapp.repositories;
import com.example.todoapp.models.Task;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TaskRepository extends JpaRepository<Task, Long> {
}
-
JpaRepository
を継承したinterfaceを定義-
JpaRepository
の祖先にいるCrudRepository
インターフェースがfindById
などのインターフェースを定義している - 実装は
SimpleJpaRepository
なのかな?
-
-
JpaRepository
は対象のエンティティクラスとその主キーの型を指定する- ここで指定した主キーの型は、
findById
やdeleteById
の引数の型として使われる - 今回作成した
Task
エンティティの主キーはLong
型なのでここではLong
を指定
- ここで指定した主キーの型は、
controllers
- ApiController.java
package com.example.todoapp.controllers;
import com.example.todoapp.models.Task;
import com.example.todoapp.repositories.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping(value = "/api")
public class ApiController {
@Autowired
TaskRepository taskrepo;
@GetMapping(value = "/todos")
public List<Task> tasklist(){
return taskrepo.findAll();
}
@GetMapping(value = "todo/{id}")
public Optional<Task> retrieve(@PathVariable Long id){
return taskrepo.findById(id);
}
@PostMapping(value = "/todo")
public ResponseEntity<Task> newTask(@RequestBody Task task){
Task result = taskrepo.save(task);
return new ResponseEntity<Task> (result, HttpStatus.CREATED);
}
@PutMapping(value = "/todo/{id}")
public Task update(@PathVariable Long id, @RequestBody Task task){
task.setId(id);
return taskrepo.save(task);
}
@DeleteMapping(value = "/todo/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id){
taskrepo.deleteById(id);
}
}
- 各アノテーションについて
-
@RestController
- 各メソッドの戻り値がそのままレスポンスボディになる
-
@Controller
を使う場合、メソッドの戻り値はStringにしてテンプレートに埋め込む形で使うらしい
-
@RequestMapping
- このコントローラが受け付けるリクエストのパスやメソッドやクエリパラメータを指定できる
-
@Autowired
-
@Component
アノテーションをつけて登録されているBeanとの紐付けを行う -
@Repository
や@Service
をつけたクラスも、内部では@Component
が付加されている - ここでは
TaskRepository
インターフェースを満たすクラスが自動的にtaskrepo
に格納される
-
-
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
-
@RequestMapping
のメソッドを固定したもの -
@GetMapping
の場合、@RequestMapping(method=RequestMethod.GET)
と同等
-
-
@ResponseStatus
- デフォルトのステータスコードを指定する
-
200
以外を返したい時に使う?
-
動作確認
新規登録
$ curl http://localhost:8080/api/todo -XPOST -H 'Content-Type: application/json' -d '{"summary": "my first task"}' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 75 0 47 100 28 170 101 --:--:-- --:--:-- --:--:-- 170
{
"id": 1,
"summary": "my first task",
"done": false
}
リスト取得
$ curl http://localhost:8080/api/todos | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 49 0 49 0 0 285 0 --:--:-- --:--:-- --:--:-- 286
[
{
"id": 1,
"summary": "my first task",
"done": false
}
]
1件取得
$ curl http://localhost:8080/api/todo/1 | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 47 0 47 0 0 2045 0 --:--:-- --:--:-- --:--:-- 2136
{
"id": 1,
"summary": "my first task",
"done": false
}
更新
$ curl http://localhost:8080/api/todo/1 -XPUT -H 'Content-Type: application/json' -d '{"summary": "updated my task", "done": true}' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 92 0 48 100 44 2632 2412 --:--:-- --:--:-- --:--:-- 2666
{
"id": 1,
"summary": "updated my task",
"done": true
}
削除(レスポンスボディがないのでStatusCodeで確認)
$ curl http://localhost:8080/api/todo/1 -XDELETE -v
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /api/todo/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204
< Date: Wed, 25 Sep 2019 15:29:37 GMT
<
* Connection #0 to host localhost left intact
まとめ
- 動かしたいもの自体はめっちゃ簡単に動かせた
- アノテーション種類多すぎて何使えばいいのかわからん
- とりあえず今回使ったものくらいは最低限おさえておきたい
- あとは何かするときに都度調べられればよさそう