Spring Boot 入門

Yusuke Shinyama, May. 2024
概要: この記事では Java/Kotlin/Spring/TDD のいずれにもなじみがない読者のために Spring Boot + Kotlin を使ったアプリ開発の基礎を紹介する。
前提知識: JavaScript を使ったプログラミング経験、および PostgreSQL の使用経験。
  1. はじめの準備
  2. Spring Initializr による雛形アプリ生成
  3. 最初のTDD - TodoControllerの作成
  4. TodoRepositoryの作成
  5. POSTエンドポイントの作成
  6. おわりに・参考資料
今回作りたいもの:

PostgreSQL + Spring Boot + Kotlin を使った To Do (やること管理) アプリのバックエンド。 以下のAPIエンドポイントを提供する:

  1. GET /todos を実行すると、データベースにある Todo 項目すべてを JSON形式で返す。
    [{"id":2, "text":"Buy milk."}, {"id":5, "text":"Clean up."}]
    
  2. POST /todos を実行すると、与えられた Todo をデータベースに追加する。 このとき、新しく追加された Todo ID を返す。
    {"text": "Hello!"}
    
  3. GET /todos/ID を実行すると、与えられた IDをもつ Todo 項目ひとつをJSON形式で返す。
    {"id":2, "text":"Buy milk."}
    
  4. DELETE /todos/ID を実行すると、与えられた IDをもつ Todo 項目をデータベースから削除する。
  5. 存在しないIDに対して GET や DELETE をおこなうと、ステータスコードとして 404 (Not Found) を返す。
    参考: HTTP レスポンスステータスコード

1. はじめの準備

  1. IntelliJ IDEA Community Edition (Ultimateではない) をダウンロードし、インストールする。
  2. Java SDK をダウンロード・インストールする。
  3. PostgreSQL が起動していることを確認する:
    $ brew services info postgresql
    postgresql@14 (homebrew.mxcl.postgresql@14)
    Running: ✔
    Loaded: ✔
    Schedulable: ✘
    
  4. データベース tododb を作成する:
    $ createdb tododb
    

2. Spring Initializr による雛形アプリ生成

  1. Spring Initializr を使って雛形アプリを生成し、ダウンロードする。
  2. IntelliJ IDEA CE で開く。
  3. application.properties を変更:
    src/main/resources/application.properties:
    spring.application.name=todoApp
    spring.datasource.url=jdbc:postgresql://localhost/tododb
    spring.datasource.username=postgres
    spring.datasource.password=postgres
    spring.datasource.driverClassName=org.postgresql.Driver
    
  4. index.html を新規作成する (staticフォルダで Command ⌘ + N):
    src/main/resources/static/index.html:
    <!DOCTYPE html>
    <html>
    <body>
    Welcome!
    </body>
    </html>
    
  5. IntelliJ の画面右上のメニューから todoApp [bootRun] を選択すると、アプリが起動する。
  6. http://localhost:8080/ にアクセスし、 "Welcome!" テキストが見えることを確認する。
    Javaの基本概念
    Java言語、classファイル、jarファイル
    Javaソースコード (xxx.java) をコンパイルすると、 classファイル (xxx.class) が生成される。 jarファイル (xxx.jar) は複数のclassファイルをまとめたもので、 JVM (Java Virtual Machine) と呼ばれる仮想マシンで実行する。
    Java ソースコード class ファイル jar ファイル
    Kotlin言語
    Javaを改良したもの。 Kotlinソースコード (xxx.kt) をclassファイルにコンパイルし、 Javaと同じく JVMで実行できる。
    Kotlin ソースコード class ファイル jar ファイル
    アノテーション (annotation)
    JavaおよびKotlinのコードには、アノテーションをつけることができる。 アノテーションはコメントに似ているが、コメントは人間が読むのに対し、 アノテーションはプログラムによって解析される。 Java/Kotlin では、クラスや関数 (メソッド)、変数に対してアノテーションをつけることができる。 Spring Boot では、アノテーションを多用している。
    @ClassAnnotation
    class MyClass {
      @MethodAnnotation
      fun foo() {
        @VariableAnnotation
        val x = 123
      }
    }
    
    JDBC (Java Database Connectivity)
    Java/KotlinでSQLデータベースにアクセスするための統一規格。 MySQL, PostgreSQL, Oracleなどのドライバが用意されており、 ドライバを切り替えることで多様なデータベースエンジンにアクセスできる。
    JPA (Java Persistence API)
    SQLを直接書かず、Java API を使ってデータベースにアクセスするための仕組み。 Hibernate ORM を使って実装されている。 今回は使わない。

コマンドラインを使ったビルドと起動

IntelliJ を使わず、コマンドラインからアプリのビルド・起動をする場合は、 以下のようにする:

$ ./gradlew build  (アプリのビルドおよびテスト)
Starting a Gradle Daemon, 1 stopped Daemon could not be reused, use --status for details
...

BUILD SUCCESSFUL in 11s
9 actionable tasks: 5 executed, 4 up-to-date

$ ./gradlew bootrun  (アプリの実行)
> Task :bootRun
  .   ____          _            __ _ _
 /∖∖ / ___'_ __ _ _(_)_ __  __ _ ∖ ∖ ∖ ∖
( ( )∖___ | '_ | '_| | '_ ∖/ _` | ∖ ∖ ∖ ∖
 ∖∖/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_∖__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.0)
...

3. 最初のテスト駆動開発 - TodoControllerの作成

  1. まず TodoAppApplicationTests.kt (最初から存在している) に、以下のように書く:
    src/test/kotlin/com/example/todoApp/TodoAppApplicationTests.kt
    package com.example.todoApp
    
    import org.hamcrest.MatcherAssert.assertThat
    import org.hamcrest.Matchers.*
    import org.junit.jupiter.api.Test
    import org.springframework.boot.test.context.SpringBootTest
    
    @SpringBootTest
    class TodoAppApplicationTests {
    
        @Test
        fun contextLoads() {
        }
    
    
    @Test fun `最初のテスト`() { assertThat(1+2, equalTo(3)) }
    }
    ぜひとも覚えたい IntelliJ IDEA の機能 その1

    コード中の赤色になっている部分Option + Return を押すと、 その問題を解決するための手段を提案してくれる。

  2. TodoAppApplicationTests を以下のように変更する:
    src/test/kotlin/com/example/todoApp/TodoAppApplicationTests.kt
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    class TodoAppApplicationTests(
    
    @Autowired val restTemplate: TestRestTemplate, @LocalServerPort val port: Int
    ) { ...
    @Test fun `GETリクエストはOKステータスを返す`() { // localhost/todos に GETリクエストを発行する。 val response = restTemplate.getForEntity("http://localhost:$port/todos", String::class.java) // レスポンスのステータスコードは OK である。 assertThat(response.statusCode, equalTo(HttpStatus.OK)) }
    ...
    Spring における「依存注入 (DI)」の考え方 - その1

    Springでは @Autowired@LocalServerPort のようなアノテーションを書くことによって、 実行時に必要な値やオブジェクトをフレームワークが自動的に作成・注入してくれるようになっている。 この機能を「依存注入 (Dependency Injection, DI)」と呼ぶ。 Spring では、コントローラーやデータベース接続など、アプリの動作に関連するほとんどのオブジェクトは DI を使って作成させる (ビジネスロジックに関連するオブジェクトを除く)。

    Application Context TestRestTemplate LocalServerPort TodoAppApplicationTests 依存 注入 依存 注入
    TestRestTemplate と LocalServerPort を TodoAppApplicationTests に注入する
  3. 新しいファイル TodoController.kt を作成し、以下のように書く:
    src/main/kotlin/com/example/todoApp/TodoController.kt
    package com.example.todoApp
    ...
    
    @RestController
    class TodoController {
    
        @GetMapping("/todos")
        fun getTodos(): String {
            return "Here are todos!"
        }
    
    }
    
    注意: ファイル先頭のパッケージ宣言を忘れるべからず

    Springのコードにおいては、ファイル先頭のパッケージ宣言

    package com.example.todoApp
    
    非常に重要である。 Springの依存注入は、同一パッケージ内のクラスをスキャンすることによって行うので、 パッケージ宣言を忘れると「あるはずのコンポーネントが見つからない」 「宣言したはずのメソッドが動作しない」という状況が発生する。

    Spring における「依存注入 (DI)」の考え方 - その2

    Springにおける @RestController@GetMapping のようなアノテーションは、 依存注入のもう一方の側面を表している。 ここまでのコードでは明示的に TodoController を作成するロジックは存在していない。 にもかかわらず、TodoController オブジェクトが作成され、動作しているように見える。

    ここでは、@RestController は、当該クラスが Spring Boot の DI候補として 利用可能であることを示す。さらに @GetMapping("/todos") は、当該メソッドが GET /todos リクエストに対するハンドラとして利用可能であることを示す。 この2つのアノテーションが存在することによって、 Spring Boot は getTodos() メソッドの周囲に HTTP リクエスト・レスポンスを処理する機構をくっつけ全体を Webサーバとして動作させる。 この仕組みを使って、数行で新しい Web API を作成することができる。

    Application Context GET /todos TodoController getTodos() リクエスト レスポンス
    依存注入を使った APIエンドポイントの作成

4. TodoRepositoryの作成

  1. マイグレーションファイルを作成する。 resources フォルダの下に db/migration というフォルダを作成し、 ここにファイルを新規作成する:
    src/main/resources/db/migration/V1__create_todos_table.sql:
    CREATE TABLE todos (
        id SERIAL PRIMARY KEY,
        text TEXT
    );
    
    データベースマイグレーションとは何か?

    一般に、アプリのビジネスロジックはデータベースの構造に依存する。 さらに、アプリを開発するにつれてデータベース構造は変化していく。 このため、データベースの変更履歴を管理する「マイグレーションツール」を使って 開発するのが一般的である。ここでは Spring との統合が容易な Flyway というツールを使っている。

    Flyway マイグレーションファイルを書くときの注意
    • ファイル名は Vxxx__yyy.sql のような形式にすること (先頭の V は大文字)。
    • 一度使われたマイグレーションファイルは内容を書き換えたり、ファイル名を変更したり、削除しないこと。
    • マイグレーションファイルがうまくいかない場合は、データベースを一度削除し、再度作成する (重要なデータが入ってない場合に限る)。
      $ dropdb tododb
      $ createdb tododb
      
  2. PostgreSQLクライアントを起動し、"todos" テーブルができていることを確認する:
    $ psql tododb
    psql (14.12 (Homebrew))
    Type "help" for help.
    
    tododb=# \d
                      List of relations
     Schema |         Name          |   Type   |  Owner   
    --------+-----------------------+----------+----------
     public | flyway_schema_history | table    | postgres
     public | todos                 | table    | postgres
     public | todos_id_seq          | sequence | postgres
    (3 rows)
    
  3. テスト時に実行させる SQL ファイルを書く:
    src/test/resources/insert_test_data.sql:
    TRUNCATE TABLE todos;
    INSERT INTO todos VALUES (1, 'foo');
    INSERT INTO todos VALUES (2, 'bar');
    
  4. APIが返す JSON型である Todoクラスを作成する (これはテストを書く必要はない)。 Todo.kt ファイルを作成し、以下のように書く:
    src/main/kotlin/com/example/todoApp/Todo.kt
    package com.example.todoApp
    
    data class Todo(val id: Long, val text: String)
    
    Kotlin の data class とは?

    Kotlin における data class は、データの格納に使うクラスを定義するためのショートカットである。 通常であれば、以下のようなクラス定義を書く必要があるが、data class の定義は自動でこれらを追加してくれる。

    class Todo {
        val id: Long
        val text: String
    
        constructor(id: Long, text: String) {
            this.id = id
            this.text = text
        }
    
        getId(): Long { 
            return id 
        }
    
        getText(): String {
            return text
        }
    
        ...
    }
    

  5. テストを TodoAppApplicationTests.kt に追加する:
    src/test/kotlin/com/example/todoApp/TodoAppApplicationTests.kt
    package com.example.todoApp
    ...
    
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    @Sql("/insert_test_data.sql")
    class TodoAppApplicationTests
        ...
    
    @Test fun `GETリクエストはTodoオブジェクトのリストを返す`() { // localhost/todos に GETリクエストを送り、レスポンスを Todoオブジェクトの配列として解釈する。 val response = restTemplate.getForEntity("http://localhost:$port/todos", Array<Todo>::class.java) // レスポンスの Content-Type は application/json であること。 assertThat(response.headers.contentType, equalTo(MediaType.APPLICATION_JSON)) // 配列は2つの要素をもつこと。 val todos = response.body!! assertThat(todos.size, equalTo(2)) // 最初の要素は id=1 であり、text が "foo" であること。 assertThat(todos[0].id, equalTo(1)) assertThat(todos[0].text, equalTo("foo")) // 次の要素は id=2 であり、text が "bar" であること。 assertThat(todos[1].id, equalTo(2)) assertThat(todos[1].text, equalTo("bar")) }
    ...

    ボタンを押してテストを実行し、失敗することを確認する。

  6. テストを書いたので、実装を開始する。 まず、データベースとの入出力をおこなうリポジトリ (Repository) オブジェクトを定義する。 TodoRepository.kt ファイルを作成し、以下のように書く:
    src/main/kotlin/com/example/todoApp/TodoRepository.kt
    package com.example.todoApp
    ...
    
    @Component
    class TodoRowMapper : RowMapper {
        override fun mapRow(rs: ResultSet, rowNum: Int): Todo {
            return Todo(rs.getLong(1), rs.getString(2))
        }
    }
    
    @Repository
    class TodoRepository(
        @Autowired val jdbcTemplate: JdbcTemplate,
        @Autowired val todoRowMapper: TodoRowMapper
    ) {
        fun getTodos(): List<Todo> {
            return jdbcTemplate.query("SELECT id, text FROM todos", todoRowMapper)
        }
    }
    
    ぜひとも覚えたい IntelliJ IDEA の機能 その2

    コード中のクラス名・関数名・変数名などを Command ⌘ キーを押しながらクリックすると、 そのクラスや関数の定義箇所に移動する。

  7. つぎに、TestController を変更してリポジトリを呼び出し、 GETメソッドの返り値を返すようなメソッドを追加する:
    src/main/kotlin/com/example/todoApp/TodoController.kt
    package com.example.todoApp
    ...
    
    @RestController
    class TodoController(val todoRepository: TodoRepository) {
        ...
    
    @GetMapping("/todos") fun getTodos(): List<Todo> { return todoRepository.getTodos() }
    ...

    TodoAppApplicationTests に戻り、 ボタンを押してテストを実行し、成功することを確認する。

なぜ依存注入が必要なのか?

なぜ Spring が「依存注入」「IoCコンテナ」などという概念を使っているのかというと、 それはコードを変えずにシステムのふるまいを変更するためである。 たとえば、以下のようなコードを考えてみよう:

name = "tododb"
db = PostgreSQL(name)
db.query("SELECT * FROM todos")

このコードでは、ふるまいは完全に固定されている。 データベース名を変更したり、PostgreSQL 以外のデータベースを使うためには コードを変更する必要がある。しかしテストの際には できるだけ本番コードそのものをテストしたほうがよい。 上のようなコードではテストが面倒である。 そこで Spring を使ったプログラミングでは、コードの各部分を分解し、 フレームワークに「配管作業」を任せている。そしてこの接続を 切り替えられるようにしておくことで、システムの柔軟性を実現している。 この配管作業が依存注入に相当する。

"tododb" PostgreSQL(name) db.query("SELECT * FROM todos") "tododb2" "otherdb" MySQL(name)
依存注入のイメージ (実線は有効な接続)

Spring における依存注入や機能拡張は、基本的にアプリケーション ロジックの各箇所に Springのコードを挿入することによって行われる。 このために、Springではアプリのロジックを分割して書く必要がある。

アプリのロジック本体 Springが 挿入した部分 実行 実行
Spring による機能拡張のイメージ

5. POSTエンドポイントの作成

  1. POSTリクエストで受け取る JSONの型を定義する:
    src/main/kotlin/com/example/todoApp/TodoRequest.kt
    package com.example.todoApp
    
    data class TodoRequest(val text: String)
    
  2. テストを書いて実装する:
    src/test/kotlin/com/example/todoApp/TodoAppApplicationTests.kt
        @Test
        fun `POSTリクエストはOKステータスを返す`() {
            // localhost/todos に POSTリクエストを送る。このときのボディは {"text": "hello"}
            val request = TodoRequest("hello")
            val response = restTemplate.postForEntity("http://localhost:$port/todos", request, String::class.java)
            // レスポンスのステータスコードは OK であること。
            assertThat(response.statusCode, equalTo(HttpStatus.OK))
        }
    
    ぜひとも覚えたい IntelliJ IDEA の機能 その3

    Kotlinファイル内で Java言語のコードをペーストすると、 IntelliJ は自動的に Java → Kotlin に変換してくれる。 これはJavaのサンプルコードをKotlinで使いたいときに役に立つ。 たとえば、以下のJavaコードを上記のメソッド内にペーストしてみよう:

    TodoRequest request = new TodoRequest("hello");
    ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:"+port+"/todos", request, String.class);
    

    ヒント: POSTリクエストを処理するためには @GetMapping ではなく @PostMapping を使う。 また、このとき @RequestBody を使って ボディとして受け取った JSON を TodoRequestオブジェクトに変換できる。

        @PostMapping("/todos")
        fun saveTodo(@RequestBody todoRequest: TodoRequest): String {
            ...
        }
    
  3. テストを書いて実装する。 このとき「正しくデータベースに追加できているか」をテストで確認するためには、以下のようにする:
    1. 最初に GET /todos を実行し、得られた Todoのリストを記憶しておく。
    2. POST /todos でなにか適当な Todoをひとつ追加する。
    3. ふたたび GET /todos を実行し、得られた Todoリストに 新しい Todo項目が追加されているかどうかをチェックする。
    src/test/kotlin/com/example/todoApp/TodoAppApplicationTests.kt
        @Test
        fun `POSTリクエストはTodoオブジェクトを格納する`() {
            // localhost/todos に GETリクエストを送り、レスポンスを Todoオブジェクトの配列として解釈する。
            ...
            // このときのレスポンスを todos1 として記憶。
            ...
    
            // localhost/todos に POSTリクエストを送る。このときのボディは {"text": "hello"}
            ...
    
            // ふたたび localhost/todos に GETリクエストを送り、レスポンスを Todoオブジェクトの配列として解釈する。
            ...
            // このときのレスポンスを todos2 として記憶。
            ...
    
            // 配列 todos2 は、配列 todos1 よりも 1 要素だけ多い。
            assertThat(todos2.size, equalTo(todos1.size + 1))
            // 配列 todos2 には "hello" をもつTodoオブジェクトが含まれている。
            assertThat(todos2.map { todo: Todo -> todo.text }, hasItem("hello"))
        }
    

    ヒント: データベースへの記録には jdbcTemplate.update メソッドを使う。 このとき SQL文中における「?」の部分が引数の値に展開される。

        jdbcTemplate.update("INSERT INTO todos (text) VALUES (?)", todoRequest.text)
    
    Spring 用語解説
    Bean と POJO
    Spring Bean は、Springによって作成・管理されるオブジェクト。 これに対してコード上で作成する (newする) オブジェクトは POJO (Plain Old Java Object) と呼ばれる。 一般に、アプリの動作に必要なオブジェクトには Bean を使い、ビジネスロジックで使うオブジェクトは POJO を使う。

    注意: "Java Bean" という用語もあるが、 これは特定の規則に従って作られた Javaオブジェクトのことをさし、 Spring Beanとは別物

    Spring IoC コンテナと Application Context
    Spring IoC (Inversion of Control) コンテナには、 Spring によって作成・管理される Bean が入っている。 Spring は必要に応じて Beanを作成し、依存注入をおこなう。 "Application Context" もほぼ同義。
    Application Context Bean Bean ...
    @Controller, @Repository, @Service, @Component など
    Spring Bean の候補となるクラスにつけられるアノテーション。 役割によって使い分ける。 Spring Boot では起動時にこれらのアノテーションが自動検出されるため、 何も設定もしなくても依存注入が可能になる。
    • @Controller (または @RestController) … Web APIのエンドポイントを提供する。
    • @Repository … データの保存・変更・削除等の機能を提供する。
    • @Service … ビジネスロジックに関連した機能を提供する。
    • @Component … 上記以外の目的で使われる。

うまく動かない場合は

コードがうまく動かない場合、以下の2つの方法で問題を特定する:

  1. ソースコード中で止めたい箇所にブレークポイントを設定し、デバッガで実行する。
  2. Spring Bootのログを調べる。 main あるいは test の application.properties に以下の行を追加して実行してみよう:
    application.properties:
    ...
    logging.level.org.springframework.web=DEBUG
    logging.level.org.springframework.jdbc.core=DEBUG
    

Spring Bootのログの読み方

アプリの実行時にターミナルに現れる Spring Bootのログは、 何かイベントが発生するたびに記録される。 ログの各行は以下のような書式になっている。

  1. タイムスタンプ: そのイベントが発生した日時。(+09:00 は日本時間を表す)
  2. ログレベル: イベントの種類。(上のものほど深刻)
    1. ERROR … 何らかのエラーが発生し、処理を完了できなかった場合。
    2. INFO … エラーではないが、ユーザに有用と思われる情報。
    3. DEBUG … 通常のユーザには必要ない、開発者がデバッグ用に使う情報。
    4. TRACE … 実行過程を詳細にトレースする場合の情報。(使用注意)

    注意: 通常は DEBUGレベルまたはTRACEレベルのログは出力すべきでない (出力量が膨大なため)。

  3. プロセスID: アプリが実行されているUNIXプロセスID。
  4. スレッド名: イベントが発生した JVMスレッドの名前。
  5. モジュール名: イベントが発生した Javaモジュール名。長い名前は省略される。
    例:
  6. メッセージ: イベントの内容。

ログ出力例 (WebおよびJDBCのレベルを DEBUGにした場合):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.0)

2024-06-11T10:08:17.591+09:00  INFO 17122 --- [    Test worker] c.e.todoApp.TodoAppApplicationTests      : Starting TodoAppApplicationTests using Java 21.0.3 with PID 17122
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^ ^^^^^      ^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  タイムスタンプ                  ログ プロセス          スレッド名    モジュール名 (c.e = com.example)              メッセージ
                              レベル   ID                       
2024-06-11T10:08:17.591+09:00  INFO 17122 --- [    Test worker] c.e.todoApp.TodoAppApplicationTests      : No active profile set, falling back to 1 default profile: "default"
2024-06-11T10:08:17.868+09:00  INFO 17122 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2024-06-11T10:08:17.879+09:00  INFO 17122 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8 ms. Found 0 JDBC repository interfaces.
2024-06-11T10:08:18.120+09:00  INFO 17122 --- [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 0 (http)
                                                                                                            (Tomcat Webサーバを起動)
2024-06-11T10:08:18.126+09:00  INFO 17122 --- [    Test worker] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-06-11T10:08:18.126+09:00  INFO 17122 --- [    Test worker] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.24]
2024-06-11T10:08:18.154+09:00  INFO 17122 --- [    Test worker] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-06-11T10:08:18.155+09:00  INFO 17122 --- [    Test worker] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 553 ms
2024-06-11T10:08:18.265+09:00  INFO 17122 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-06-11T10:08:18.353+09:00  INFO 17122 --- [    Test worker] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@77c1e611
                                                                                                            (Hikari Connection Pool が PostgreSQLに接続)
2024-06-11T10:08:18.354+09:00  INFO 17122 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-06-11T10:08:18.377+09:00  INFO 17122 --- [    Test worker] org.flywaydb.core.FlywayExecutor         : Database: jdbc:postgresql://localhost/tododb (PostgreSQL 14.12)
2024-06-11T10:08:18.388+09:00  INFO 17122 --- [    Test worker] o.f.c.i.s.JdbcTableSchemaHistory         : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet
2024-06-11T10:08:18.390+09:00  INFO 17122 --- [    Test worker] o.f.core.internal.command.DbValidate     : Successfully validated 1 migration (execution time 00:00.005s)
2024-06-11T10:08:18.393+09:00  INFO 17122 --- [    Test worker] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2024-06-11T10:08:18.412+09:00  INFO 17122 --- [    Test worker] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2024-06-11T10:08:18.414+09:00  INFO 17122 --- [    Test worker] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version "1 - create todos table"
2024-06-11T10:08:18.422+09:00  INFO 17122 --- [    Test worker] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.002s)
                                                                                                            (Flyway がマイグレーションを version 1 まで完了)
2024-06-11T10:08:18.482+09:00  INFO 17122 --- [    Test worker] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2024-06-11T10:08:18.511+09:00 DEBUG 17122 --- [    Test worker] s.w.s.m.m.a.RequestMappingHandlerMapping : 7 mappings in 'requestMappingHandlerMapping'
2024-06-11T10:08:18.563+09:00 DEBUG 17122 --- [    Test worker] o.s.w.s.handler.SimpleUrlHandlerMapping  : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'
                                                                                                            (各リクエストURLの登録が完了)
2024-06-11T10:08:18.581+09:00 DEBUG 17122 --- [    Test worker] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2024-06-11T10:08:18.595+09:00 DEBUG 17122 --- [    Test worker] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2024-06-11T10:08:18.697+09:00  INFO 17122 --- [    Test worker] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 58245 (http) with context path '/'
2024-06-11T10:08:18.702+09:00  INFO 17122 --- [    Test worker] c.e.todoApp.TodoAppApplicationTests      : Started TodoAppApplicationTests in 1.221 seconds (process running for 1.786)
2024-06-11T10:08:19.317+09:00  INFO 17122 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-06-11T10:08:19.317+09:00  INFO 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2024-06-11T10:08:19.317+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Detected StandardServletMultipartResolver
2024-06-11T10:08:19.317+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Detected AcceptHeaderLocaleResolver
2024-06-11T10:08:19.317+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Detected FixedThemeResolver
2024-06-11T10:08:19.317+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@563392e5
2024-06-11T10:08:19.318+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.support.SessionFlashMapManager@244f356
2024-06-11T10:08:19.318+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2024-06-11T10:08:19.318+09:00  INFO 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2024-06-11T10:08:19.321+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : GET "/todos/2", parameters={}
                                                                                                            (GETリクエストを受け取った)
2024-06-11T10:08:19.326+09:00 DEBUG 17122 --- [o-auto-1-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.todoApp.TodoController#getTodo(long)
2024-06-11T10:08:19.366+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2024-06-11T10:08:19.367+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT id, text FROM todos WHERE id=?]
                                                                                                            (SQL文を実行した)
2024-06-11T10:08:19.386+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [*/*] and supported [application/json, application/*+json]
2024-06-11T10:08:19.387+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [Todo(id=2, text=bar)]
2024-06-11T10:08:19.392+09:00 DEBUG 17122 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 200 OK
                                                                                                            (ステータスコード200でレスポンスを返した)
2024-06-11T10:08:19.403+09:00 DEBUG 17122 --- [    Test worker] o.s.web.client.DefaultRestClient         : Reading to [com.example.todoApp.Todo]

6. おわりに

Spring Boot の長所:

Spring Boot の短所:

今後の改良点

このチュートリアルは簡単のため、本来であれば推奨されるいくつかの作業をしていない。 この例を参考に本格的なアプリを作る場合は、以下の改良点を参考のこと:

参考資料

とほほのKotlin入門
Kotlinの文法まとめ。
とほほのPostgreSQL入門
PostgreSQLの文法まとめ。
Spring Boot リファレンスマニュアル
RestTemplate
JdbcTemplate
Spring Boot 各種クラス・メソッドのリファレンス文書。
Spring Bootでよく使うアノテーション一覧
Spring Boot でよく使うアノテーションまとめ。
Hamcrestチュートリアル (英語)
テストで使う assertThat の書き方。
Spring Academy (英語)
Spring Boot を詳しく学びたい人向けのサイト。多数の動画・例題および実験用ラボが用意されている。

Yusuke Shinyama