🫖

Kotlin + SpringWeb + Spring Data JDBC で Web API サーバーを作成する

2022/10/27に公開約8,200字

概要

本記事では、Java の O/R マッパの 1 つである Spring Data JDBC を用いて、簡単な Web API サーバーを作成する例を紹介します。

以下の構成で紹介していきます。

  • 「Spring Data JDBC について」
  • 「Kotlin(Sprint Boot)+ Spring Data JDBC で Web API サーバーを作成」
  • 「まとめ」

本記事で作成したサンプルコードは以下です。

https://github.com/Msksgm/kotlin-spring-data-jdbc-web-api-demo

Spring Data JDBC について

Sprint Data JDBC は、Spring Data の 1 種類です。
Spring Data は様々な手法目的(RDS、NoSQL、Reactive、Redis)に対して、Spring のように記述可能なインタフェースの提供を目的にしています。

https://spring.io/projects/spring-data

Spring Data JDBC は JDBC をベースにしたデータアクセスを目的としています。
JDBC の単純さ(遅延読み込みやキャシュをおこなわないなど)を残しつつ、簡潔なメソッド(12 種類のみ)の提供と、それ以上の操作は@Queryアノテーションによる自作によって対応します。

https://spring.io/projects/spring-data-jdbc

他にも、ドメイン駆動設計(DDD)の「リポジトリ」「集約」「集約ルート」の概念に影響を受けた実装になっています。
詳細な説明を省きますので、気になる方は以下のリファレンスを参考にしてみてください。

https://spring.pleiades.io/spring-data/jdbc/docs/current/reference/html/#jdbc.repositories

Kotlin(Sprint Boot)+ Spring Data JDBC で Web API サーバーを作成

Spring Initializr

Spring Initializr を用いて、雛形を作成します。
以下のリンクから、Spring Initializr の画面を開いてください。

https://start.spring.io/

設定の項目は以下になります。
「GENERATE」を押下して、zip ファイルをダウンロードしたら、自身のディレクトリに配置して解凍してください。

項目 要素
Project Gradle Project
Language Kotlin
Spring Boot 2.7.5
Project Metadata Artifact kotlin-spring-data-jdbc-web-api-demo
Project Metadata Name kotlin-spring-data-jdbc-web-api-demo
Packaging Jar
Java 17
Dependencies Spring Web、Spring Data JDBC、PostgreSQL Driver

Spring Initializr
Spring Initializr

DB

DB は docker compose を利用します。

docker-compose.yml は以下を利用してください。

今回使用する docker-compose.yml。
./docker-compose.yml
---

version: '3.8'

services:
  #
  # PostgreSQL
  #
  sample-pg:
    image: postgres:14-bullseye
    container_name: sample-pg
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: sample-user
      POSTGRES_PASSWORD: sample-pass
      POSTGRES_DB: sample-db
      POSTGRES_INIT_DB_ARGS: --encoding=UTF-8
    volumes:
      - type: bind
        source: ${PWD}/sql/
        target: /docker-entrypoint-initdb.d/

初期値を挿入するために、SQL を配置してください。

初期値挿入のための SQL。
./sql/001-customer.sql
DROP TABLE IF EXISTS customer;
CREATE TABLE IF NOT EXISTS customer (
    id SERIAL
    , first_name VARCHAR(255)
    , last_name VARCHAR(255)
);

INSERT INTO customer ( first_name, last_name ) VALUES
    ( 'Alice', 'Sample1' )
    , ( 'Bob', 'Sample2' )
;

実装

これから、Spring Data JDBC の基本的な実装を説明します。
以下の流れでおこないます。

  • DB のテーブルに該当するエンティティを定義
  • CrudRepository<T, ID>を実装した リポジトリ interface を作成
  • コントローラーやサービスで、リポジトリを使用

エンティティ

テーブルに該当するエンティティを定義します。
以下の実装では、customer テーブルに相当するデータクラスCustomerを実装しました。
テーブル名を@Tableで指定し、オートインクリメントをしているカラムには@Idを指定します。

src/main/kotlin/com/example/kotlinspringdatajdbcwebapidemo/entity/Customer.kt
import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table

@Table("customer")
data class Customer(
    @Id val id: Int,
    val firstName: String,
    val lastName: String,
)

他のアノテーションについては以下を参照してください。

https://spring.pleiades.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/package-summary.html

リポジトリ

Spring JDBC では、CrudRepositoryを実装することで、エンティティに CRD 処理の実行可能なリポジトリを作成可能です。
メソッド自体の準備はこれで完了です。
CrudRepositoryに定義されている 12 種類のメソッドをエンティティに対して実行可能です。
12 種類の中に Update の処理は含まれておらず、自作するには @Queryを利用する必要があります。

src/main/kotlin/com/example/kotlinspringdatajdbcwebapidemo/repository/CustomerRepository.kt
import com.example.kotlinspringdatajdbcwebapidemo.entity.Customer
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository

@Repository
interface CustomerRepository : CrudRepository<Customer, Int>

コントローラー

コントローラーを用意して、DB 操作をおこないます。
それぞれ以下の API メソッドを用意しています。

メソッド パス 用途
GET /customers/{id} 単一取得
GET /customers 全件取得
POST /customers 追加
DELETE /customers/{id} 削除
src/main/kotlin/com/example/kotlinspringdatajdbcwebapidemo/controller/CustomerController.kt
import com.example.kotlinspringdatajdbcwebapidemo.entity.Customer
import com.example.kotlinspringdatajdbcwebapidemo.repository.CustomerRepository
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class CustomerController(private val customerRepository: CustomerRepository) {
    @GetMapping("/customers/{id}")
    fun getById(@PathVariable id: Int) = customerRepository.findById(id)

    @GetMapping("customers")
    fun getList(): MutableIterable<Customer> = customerRepository.findAll()

    @PostMapping("/customers")
    fun insert(@RequestBody customer: Customer) = customerRepository.save(customer)

    @DeleteMapping("/customers/{id}")
    fun delete(@PathVariable id: Int) = customerRepository.deleteById(id)
}

動作確認

実装自体は完了ですが、念の為動作確認します。

DB の起動

以下のコマンドで起動してください。

コンテナの起動
docker compose up
db を止めて、コンテナを削除するとき。
コンテナの停止
# Ctrl + c で止めてから
docker compose down

サーバーの起動

Sprint Boot は以下のコマンドで起動できます。
http://localhost:8080でサーバーが起動されます。

サーバーの起動
./gradlew bootRun

動作確認

それぞれのリクエストでの動作確認の方法をまとめました。
それぞれ折り畳んだので、確認してみてください。

単一取得。
単一取得
curl --location --request GET 'http://localhost:8080/customers/1' | jq .
レスポンス
{
  "id": 1,
  "firstName": "Alice",
  "lastName": "Sample1"
}
全件取得。
全件取得
curl --location --request GET 'http://localhost:8080/customers' | jq .
レスポンス
[
  {
    "id": 1,
    "firstName": "Alice",
    "lastName": "Sample1"
  },
  {
    "id": 2,
    "firstName": "Bob",
    "lastName": "Sample2"
  }
]
追加。
追加
curl --location --request POST 'http://localhost:8080/customers' \
--header 'Content-Type: application/json' \
--data-raw '{
    "firstName": "Carol",
    "lastName": "Sample3"
}' | jq .
レスポンス
{
  "id": 3,
  "firstName": "Carol",
  "lastName": "Sample3"
}
確認用全件取得
curl --location --request GET 'http://localhost:8080/customers' | jq .
レスポンス
[
  {
    "id": 1,
    "firstName": "Alice",
    "lastName": "Sample1"
  },
  {
    "id": 2,
    "firstName": "Bob",
    "lastName": "Sample2"
  },
  {
    "id": 3,
    "firstName": "Carol",
    "lastName": "Sample3"
  }
]

削除。
削除
curl --location --request DELETE 'http://localhost:8080/customers/3' | jq .
レスポンスなし
全件取得
curl --location --request GET 'http://localhost:8080/customers' | jq .
レスポンス
[
  {
    "id": 1,
    "firstName": "Alice",
    "lastName": "Sample1"
  },
  {
    "id": 2,
    "firstName": "Bob",
    "lastName": "Sample2"
  }
]

まとめ

Spring Data を利用した、Web API の作成方法を紹介しました。
DB のテーブル定義から簡単に CRD の処理を記述可能なことがわかりました。

個人的な感想としては、Repository に Spring Data の interface を継承させる実装について気になりました。DDD では、ドメイン層に技術を依存させるのを避けるため、どのように扱うのか気になりました。

本記事の紹介した範囲では、テーブルとモデルが密結合していたり、JOIN や UPDATE ができなかったりするので、是非アレンジしてみてください。

参考

Spring Data を用いた主な実装方法は以下を参考にしました。

https://gihyo.jp/book/2020/978-4-297-10917-2

他は、公式リファレンスです。

https://spring.io/projects/spring-data

https://spring.io/projects/spring-data-jdbc

https://spring.pleiades.io/spring-data/jdbc/docs/current/reference/html/#jdbc.repositories

Discussion

ログインするとコメントできます