1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker 環境と Java Spring Boot で最小 API を実装する:MySQL

Last updated at Posted at 2023-08-31

Docker 環境と Java Spring Boot で最小 API を実装する:MySQL

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 24.04 で Java Spring Boot Web アプリケーションを作成し、最小限の REST API を実装する方法を紹介します。
spring-boot_and_mysql_on_docker.png

目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

実現すること

ローカル環境の Ubuntu の Docker 環境で、Dockerfile からビルドした Java Spring Boot Web サービスのカスタムコンテナーと MySQL データベースコンテナーを起動します。

JARファイル形式のアプリをコンテナーとして起動

実行環境

要素 概要
terminal ターミナル
Ubuntu OS
Docker コンテナー実行環境

REST API サービス コンテナー

要素 概要
api-todo-spring-boot REST API サービス カスタムコンテナー
JVM Java 実行環境
app.jar Java アプリケーション
tomcat Web サーバー

データベース コンテナー

要素 概要
mysql-todo データベースコンテナー
mysql DB サーバー
db_todo データベース

技術トピック

Spring Boot とは?

こちらを展開してご覧いただけます。

Spring Boot (スプリングブート)

Spring Boot は Java アプリケーションの迅速な開発とデプロイをサポートし、一般的な開発作業や設定にかかる手間を軽減するためのツールや機能を提供するフレームワークです。

キーワード 内容
簡易なセットアップ Spring Boot は、設定を最小限に抑え、デフォルトの設定を提供することで、アプリケーションのセットアップを簡単にします。アノテーションや自動構成により、煩雑な設定作業を減少させます。
マイクロサービス向け Spring Boot は、マイクロサービスアーキテクチャをサポートしており、単一のアプリケーションから複数の独立したサービスを構築しやすくなっています。
埋め込みサーバー Web アプリケーションをビルドする際、Tomcat や Jetty などのウェブサーバーをアプリケーションに内蔵できます。これにより、独立して実行可能な JAR ファイルを生成できます。
自動設定 Spring Boot は、アプリケーションのクラスパスや使用されるライブラリに基づいて自動的に設定を行います。これにより、煩雑な設定ファイルの作成を減少させ、開発者が本質的なコードに集中できます。
プロダクション準備 Spring Boot は、プロダクション環境での運用に必要な機能を提供します。メトリクス、ヘルスチェック、外部設定など、アプリケーションの監視と管理を容易にします。
豊富なスターター Spring Boot は、アプリケーション開発においてよく使用される依存関係をまとめた「スターター」を提供しています。これにより、特定の用途に適した設定を簡単に取り入れることができます。
カスタマイズ可能 Spring Boot は、デフォルトの動作をカスタマイズするためのオプションを提供します。これにより、特定のニーズや要件に合わせた調整が可能です。

開発環境

  • Windows 11 Home 23H2 を使用しています。

WSL の Ubuntu を操作しますので macOS の方も参考にして頂けます。

WSL (Microsoft Store アプリ版) ※ こちらの関連記事からインストール方法をご確認いただけます

> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61

Ubuntu ※ こちらの関連記事からインストール方法をご確認いただけます

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble

Java JDK ※ こちらの関連記事からインストール方法をご確認いただけます

$ java -version
openjdk version "17.0.12" 2024-07-16
OpenJDK Runtime Environment (build 17.0.12+7-Ubuntu-1ubuntu224.04)
OpenJDK 64-Bit Server VM (build 17.0.12+7-Ubuntu-1ubuntu224.04, mixed mode, sharing)

Maven ※ こちらの関連記事からインストール方法をご確認いただけます

$ mvn -version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 17.0.12, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64

Docker ※ こちらの関連記事からインストール方法をご確認いただけます

$ docker --version
Docker version 27.0.3, build 7d4bcd8

この記事では基本的に Ubuntu のターミナルで操作を行います。Vim を使用してコピペする方法をはじめて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。

作成する REST API の仕様

エンドポイント HTTPメソッド 説明 リクエストBody レスポンスBody
/todos GET すべての ToDo アイテムを取得します。 None ToDo アイテムの配列
/todos/complete GET 完了した ToDo アイテムを取得します。 None ToDo アイテムの配列
/todos/{id} GET ID で ToDo アイテムを取得します。 None ToDo アイテム
/todos POST 新しい ToDo アイテムを追加します。 ToDo アイテム ToDo アイテム
/todos/{id} PUT 既存の ToDo アイテムを更新します。 ToDo アイテム None
/todos/{id} DELETE ID で ToDo アイテムを削除します。 None None

データベース コンテナーの起動

こちらの記事で、ToDo アプリ用の RDBMS データベースを作成し、Docker コンテナーとして起動する手順をご確認いただけます。

データベース コンテナーが起動していることを確認します。

$ docker start mysql-todo
$ docker ps
CONTAINER ID   IMAGE        COMMAND                   CREATED        STATUS       PORTS                                                  NAMES
63a3acec271c   mysql-base   "docker-entrypoint.s…"   20 hours ago   Up 3 hours   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql-todo

コンテナー間通信するために net-todo という Docker ネットワークをあらかじめ作成しています。ご注意ください。

REST API を実装する手順

プロジェクトの作成

プロジェクトフォルダーを作成します。
※ ~/tmp/restapi-spring-boot をプロジェクトフォルダーとします。

$ mkdir -p ~/tmp/restapi-spring-boot
$ cd ~/tmp/restapi-spring-boot

Model クラスの作成

このシリーズの記事で、RDBMSNoSQL の両方で同じ API 操作を行います。そのため、ID フィールドは数値型ではなく文字列として定義しています。統一性を保ちつつ異なるデータストアで動作することを目指しています。ご了承ください。

Todo エンティティクラスを作成します。

$ mkdir -p src/main/java/com/example/springboot/model
$ vim src/main/java/com/example/springboot/model/Todo.java

ファイルの内容

model/Todo.java
package com.example.springboot.model;

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import lombok.Data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

// ToDo エンティティを表すクラス
@Data
@Entity @Table(name="todos")
public class Todo {
    // RDBMS 本来の int 型のキー:API 入出力の JSON にはマッピングしない
    @JsonIgnore
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Id @Column(name="id", unique=true) Long _rdbms_id;

    // string 型の ID を定義:RDBMS 側にはマッピングしない
    @Transient String id;
    public String getId() { return Long.toString(_rdbms_id); }
    public void setId(String id) { _rdbms_id = Long.parseLong(id); }

    @JsonProperty("content")
    @Column(name="content") String content;

    @JsonProperty("created_date")
    @Column(name="created_date") 
    @Temporal(TemporalType.TIMESTAMP) Date createdDate;

    @JsonProperty("completed_date")
    @Column(name="completed_date") 
    @Temporal(TemporalType.TIMESTAMP) Date completedDate;
}
説明を開きます。
要素 説明
@Data Lombok ライブラリを使用する際に使用されるアノテーションです。Lombok は、Javaクラスの冗長なコードを削減するためのアノテーションベースのライブラリです。
@Entity クラスが JPA エンティティ (データベース内のテーブルにマッピングされるオブジェクト) であることを示すアノテーションです。
@Table(name="todos") エンティティが todos テーブルにマッピングされることを示すアノテーションです。テーブル名を指定しています。
@Id プライマリキー(主キー)フィールドであることを示すアノテーションです。
@GeneratedValue(strategy=GenerationType.IDENTITY) プライマリキーの値が自動的に生成される方法を指定するアノテーションです。IDENTITY 戦略は、データベースの自動増分カラムを使用してキーを生成することを示します。
@Column(name="id", unique=true) フィールドがデータベースのカラムにマッピングされることを示すアノテーションです。unique=true はカラムの値が一意であることを指定しています。
@Transient フィールドがデータベースのカラムにマッピングされないことを示すアノテーションです。トランジェントなフィールドです。
@Temporal(TemporalType.TIMESTAMP) フィールドが日付と時刻を持つことを示すアノテーションです。TIMESTAMP型を使用します。
@JsonProperty("content") JSON シリアライズ時にフィールド名を指定するためのアノテーションです。
@JsonIgnore JSONシリアライズやデシリアライズする際に、特定のフィールドを無視するためのアノテーションです。

これらの要素を組み合わせることで、Todo クラスは JPA エンティティとして機能し、データベース内の todos テーブルと対応するオブジェクトとして使用されます。

TodoRepository リポジトリインタフェースを作成します。

$ mkdir -p src/main/java/com/example/springboot/model
$ vim src/main/java/com/example/springboot/model/TodoRepository.java

ファイルの内容

model/TodoRepository.java
package com.example.springboot.model;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

// ToDo エンティティに対する Repository インタフェース
public interface TodoRepository extends JpaRepository<Todo, Long> {
    List<Todo> findByCompletedDateIsNotNull();
}
説明を開きます。

Spring Data JPA を使用してデータベースとのやり取りを行うためのリポジトリインタフェースです。このインタフェースを作成することで、基本的な CRUD 操作 (Create, Read, Update, Delete) や特定の条件でのデータ取得などを簡単に実装できるようになります。

要素 説明
TodoRepository インタフェースの宣言 インタフェース TodoRepository は JpaRepository インタフェースを拡張しています。JpaRepository は Spring Data JPA が提供する基本的なリポジトリインタフェースで、基本的な CRUD 操作が含まれています。
ジェネリクス型パラメーター JpaRepository を拡張する際、ジェネリクス型パラメーターにエンティティクラスの型 (Todo) と、エンティティの主キーの型 (Long) を指定します。
findByCompletedDateIsNotNull メソッドの宣言 このメソッドは completedDate フィールドが非 null (つまり、完了したタスク) の Todo エンティティを取得するためのメソッドです。findByCompletedDateIsNotNull という命名規則にしたがっているため、Spring Data JPA が自動的にクエリを生成し、対応するデータベース操作を行います。

Application、Controller クラスの作成

Application クラスを作成します。

$ vim src/main/java/com/example/springboot/Application.java

ファイルの内容

Application.java
package com.example.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.scheduling.annotation.EnableAsync;

@EntityScan("com.example.springboot.*")
@EnableJpaRepositories("com.example.springboot.*")
@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
説明を開きます。
要素 説明
@EntityScan("com.example.springboot.*") エンティティクラスのパッケージを指定しています。指定したパッケージ内に存在するエンティティクラスがスキャンされ、データベースとのマッピングが行われます。
@EnableJpaRepositories("com.example.springboot.*") Spring Data JPA リポジトリのパッケージを指定しています。指定したパッケージ内のリポジトリが有効化され、データベース操作のためのメソッドが提供されます。
@SpringBootApplication Spring Boot アプリケーションのエントリーポイントとして指定されています。このアノテーションにより、Spring Boot アプリケーションの設定やコンポーネントスキャンが行われます。
@EnableAsync Spring Framework のアプリケーションで非同期処理をサポートするためのアノテーションです。

TodoController クラスを作成します。

$ vim src/main/java/com/example/springboot/TodoController.java

ファイルの内容

TodoController.java
package com.example.springboot;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.CrossOrigin;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.springboot.model.Todo;
import com.example.springboot.model.TodoRepository;

@CrossOrigin( // CORS 設定:適切に修正してください。
    origins = "*", 
    methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE }
)
@RestController
@RequestMapping("/todos")
public class TodoController {

    private final TodoRepository _todoRepository;

    public TodoController(TodoRepository todoRepository) {
        _todoRepository = todoRepository;
    }

    // すべての ToDo アイテムを取得します。
    @GetMapping
    @Async public CompletableFuture<List<Todo>> getAllTodos() {
        List<Todo> todos = _todoRepository.findAll();
        return CompletableFuture.completedFuture(todos);
    }

    // 完了した ToDo アイテムを取得します。
    @GetMapping("/complete")
    @Async public CompletableFuture<List<Todo>> getCompleteTodos() {
        List<Todo> todos = _todoRepository.findByCompletedDateIsNotNull();
        return CompletableFuture.completedFuture(todos);
    }

    // ID で ToDo アイテムを取得します。
    @GetMapping("/{id}")
    @Async public CompletableFuture<Optional<Todo>> getTodo(@PathVariable String id) {
        Optional<Todo> todo = _todoRepository.findById(Long.parseLong(id));
        return CompletableFuture.completedFuture(todo);
    }

    // 新しい ToDo アイテムを追加します。
    @PostMapping
    @Async public CompletableFuture<Todo> createTodo(@RequestBody Todo todo) {
        todo.setCreatedDate(new Date());
        return CompletableFuture.completedFuture(_todoRepository.save(todo));
    }

    // 既存の ToDo アイテムを更新します。
    @PutMapping("/{id}")
    @Async public CompletableFuture<Todo> updateTodo(@PathVariable String id, @RequestBody Todo todo) {
        Optional<Todo> exist = _todoRepository.findById(Long.parseLong(id));
        if (exist.isPresent()) {
            Todo target = exist.get();
            target.setContent(todo.getContent());
            target.setCompletedDate(todo.getCompletedDate());
            return CompletableFuture.completedFuture(_todoRepository.save(target));
        }
        return CompletableFuture.completedFuture(null);
    }

    // ID で ToDo アイテムを削除します。
    @DeleteMapping("/{id}")
    @Async public CompletableFuture<Void> deleteTodo(@PathVariable String id) {
        _todoRepository.deleteById(Long.parseLong(id));
        return CompletableFuture.completedFuture(null);
    }
}
説明を開きます。
要素 説明
@CrossOrigin CORS (Cross-Origin Resource Sharing) の設定を行っています。異なるオリジンからのリクエストを許可するために、オリジンと許可する HTTP メソッドを指定しています。
RequestMethod 特定の HTTP メソッドに対してコントローラーのメソッドをマッピングするために使用されます。
@RestController コントローラクラスであることを示すアノテーションです。HTTP リクエストを処理するエンドポイントを提供するために使用されます。
@RequestMapping("/todos") クラスレベルでのリクエストマッピングを指定しています。このコントローラー内のエンドポイントは、/todos のパスにマッピングされます。
@GetMapping HTTP GET リクエストに対するハンドラーメソッドを指定しています。/todos エンドポイントでリクエストがあった際に、getAllTodos() メソッドが実行されます。
@PostMapping HTTP POST リクエストに対するハンドラーメソッドを指定しています。新しい ToDo アイテムを追加する際に、createTodo() メソッドが実行されます。
@PutMapping HTTP PUT リクエストに対するハンドラーメソッドを指定しています。既存の ToDo アイテムを更新する際に、updateTodo() メソッドが実行されます。
@DeleteMapping HTTP DELETE リクエストに対するハンドラーメソッドを指定しています。ToDo アイテムを削除する際に、deleteTodo() メソッドが実行されます。
@RequestBody HTTP リクエストのボディ部分から送信されたデータを Java オブジェクトにマッピングするために使用されます。コントローラー内のメソッドの引数として使用され、リクエストのボディ部分のデータを Java オブジェクトに変換して提供します。
@PathVariable URL パス内の変数部分をメソッドの引数として受け取るために使用されます。たとえば、/todos/{id} のような URL で、@PathVariable アノテーションを使用して {id} 部分の値をメソッドの引数として取得できます。
@Async 非同期メソッドを宣言する際に使用されます。Spring Framework によって提供される非同期処理の機能を利用して、メソッドが非同期に実行されることを示します。
CompletableFuture Java の標準ライブラリで提供されるクラスで、非同期プログラミングや非同期処理の結果の取り扱いを容易にするための仕組みです。

Spring Boot の設定ファイルを追加

application.properties ファイルを作成します。

$ mkdir -p src/main/resources
$ vim src/main/resources/application.properties

ファイルの内容

application.properties
spring.datasource.url=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.show-sql=false
説明を開きます。
要素 説明
spring.datasource.url データベースへの接続 URL を指定します。${DB_HOST}、${DB_HOST} と ${DB_PORT} と ${DB_NAME} などのプレースホルダーを使用して、実際の値が環境変数から取得されます。
spring.datasource.username データベースへの接続に使用するユーザー名を指定します。${DB_USER} プレースホルダーを使用して、実際の値が環境変数から取得されます。
spring.datasource.password データベースへの接続に使用するパスワードを指定します。${DB_PASSWORD} プレースホルダーを使用して、実際の値が環境変数から取得されます。
spring.jpa.show-sql JPA によるクエリのログを表示するかどうかを指定します。この例では false に設定されており、クエリのログは表示されません。

pom.xml の作成

pom.xml ファイルを作成します。

$ vim pom.xml

ファイルの内容

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.15</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>restapi-spring-boot</artifactId>
    <version>1.0</version>
    <name>restapi-spring-boot</name>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
説明を開きます。
要素 説明
modelVersion Maven モデルのバージョンを指定します。通常は 4.0.0 を指定します。
parent このプロジェクトの親プロジェクトを指定します。spring-boot-starter-parent は、Spring Boot プロジェクトの親プロジェクトであり、バージョン情報やビルド設定が事前に設定されています。
groupId, artifactId, version, name プロジェクトの Maven のグループ ID、アーティファクト ID、バージョン、プロジェクト名を指定します。
properties プロジェクト全体で使用されるプロパティを指定します。たとえば、Java バージョンやプロジェクトのエンコーディングなどがここで設定されています。
dependencies プロジェクトが依存するライブラリ(依存関係)を指定します。各 タグでライブラリの情報を指定します。このプロジェクトでは、Spring Boot Web 開発に必要な依存関係 (spring-boot-starter-web、spring-boot-starter-data-jpa) や JPA 関連のクラスやインターフェイス (javax.persistence-api)、MySQL データベースとの接続に関する依存関係 (mysql-connector-java)、Lombok ライブラリ(lombok) などが指定されています。
build ビルドに関連する設定を指定します。この中には、プロジェクトのファイナル名やビルドプラグインの設定が含まれています。このプロジェクトでは、spring-boot-maven-plugin を使用して Spring Boot アプリケーションをビルドするための設定が指定されています。

アプリの起動

環境変数を作成します。

export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=db_todo
export DB_USER=root
export DB_PASSWORD=password

この環境変数が一時的なものであることに注意してください。

アプリを起動します。
※ アプリを停止するときは ctrl + C を押します。

この例では、アプリがリッスンするポート番号5000に設定して起動しています。

$ mvn spring-boot:run \
    -Dspring-boot.run.arguments="--server.port=5000"

ここまでの手順で、Ubuntu でアプリを起動することができました。

アプリの動作確認

別ターミナルから curl コマンドで確認します。

必要な場合、jq をインストールします。

$ sudo apt update
$ sudo apt install jq

GET: /todos エンドポイントの動作確認

すべての ToDo アイテムを取得します。

$ curl -s http://localhost:5000/todos | jq '.'

レスポンス

[
// 省略
  {
    "id": "3",
    "content": "運動する",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": "2024-08-11T11:02:02.000+00:00"
  },
  {
    "id": "4",
    "content": "本を読む",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": null
  },
  {
    "id": "5",
    "content": "請求書を支払う",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": null
  },
// 省略

すべての ToDo アイテムを取得できています。

GET: /todos/complete エンドポイントの動作確認

完了した ToDo アイテムを取得します。

$ curl -s http://localhost:5000/todos/complete | jq '.'

レスポンス

[
  {
    "id": "1",
    "content": "食材を買う",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": "2024-08-11T11:02:02.000+00:00"
  },
  {
    "id": "2",
    "content": "報告書を仕上げる",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": "2024-08-11T11:02:02.000+00:00"
  },
  {
    "id": "3",
    "content": "運動する",
    "created_date": "2024-08-11T11:02:02.000+00:00",
    "completed_date": "2024-08-11T11:02:02.000+00:00"
  }
]

完了した ToDo アイテムを取得できています。

GET: /todos/{id} エンドポイントの動作確認

ID で ToDo アイテムを取得します。

$ curl -s http://localhost:5000/todos/8 | jq '.'

レスポンス

{
  "id": "8",
  "content": "コードを書く",
  "created_date": "2024-08-11T11:02:02.000+00:00",
  "completed_date": null
}

ID で ToDo アイテムを取得できています。

POST: /todos エンドポイントの動作確認

新しい ToDo アイテムを追加します。

$ curl -s -X POST http://localhost:5000/todos \
    -H 'Content-Type: application/json; charset=utf-8' \
    -d \
'{
    "content": "昼寝をする"
}' | jq '.'

レスポンス

{
  "id": "9",
  "content": "昼寝をする",
  "created_date": "2024-08-12T03:17:51.572+00:00",
  "completed_date": null
}

新しい ToDo アイテムが追加できています。

PUT: /todos/{id} エンドポイントの動作確認

既存の ToDo アイテムを更新します。

$ curl -s -X PUT http://localhost:5000/todos/9 \
    -H 'Content-Type: application/json; charset=utf-8' \
    -d \
'{
    "content": "窓を開ける"
}' | jq '.'

レスポンス

{
  "id": "9",
  "content": "窓を開ける",
  "created_date": "2024-08-12T03:17:52.000+00:00",
  "completed_date": null
}

対象のレコードが更新されています。

DELETE: /todos/{id} エンドポイントの動作確認

ID で ToDo アイテムを削除します。

$ curl -s -X DELETE http://localhost:5000/todos/9 | jq '.'

レスポンス

※なし

レスポンスはなしですが、DB のテーブルを確認すると対象のレコードが削除されています。

ここまでの手順で、最小限の CRUD 操作を行う REST API をアプリに実装できました。

コンテナーイメージの作成

Dockerfile を作成します。

$ vim Dockerfile

ファイルの内容

Dockerfile
# build the app.
FROM openjdk:17-jdk-slim as build-env

# set the working dir.
WORKDIR /app

# install build tools and libraries.
RUN apt-get update && apt-get install -y maven

# copy source code to the working dir.
COPY . .

# build the app.
RUN mvn clean package

# set up the container.
FROM debian:12-slim

# set the working dir.
WORKDIR /app

# install the openjdk-17-jre-headless and clean up unnecessary files.
RUN apt-get update && \
    apt-get install -y --no-install-recommends openjdk-17-jre-headless && \
    apt-get autoremove -y && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# set environment variables.
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/jre
ENV PATH=$PATH:$JAVA_HOME/bin

# copy the built app from the build-env.
COPY --from=build-env /app/target/*.jar app.jar

# expose the port.
EXPOSE 8080

# command to run the app using java.
ENTRYPOINT ["java","-jar","app.jar"]
説明を開きます。
要素 説明
FROM openjdk:17-jdk-slim as build-env ビルド環境のベースイメージとして、OpenJDK 17 の JDK 版を使用します。
WORKDIR /app 作業ディレクトリを /app に設定します。
RUN apt-get update && apt-get install -y maven ビルドに必要なパッケージである Maven をインストールします。apt-get update でパッケージ情報を更新し、apt-get install -y maven で Maven をインストールします。
COPY . . ソースコードと pom.xml が存在するディレクトリの内容を、作業ディレクトリにコピーします。
RUN mvn clean package Maven を使用して、アプリケーションをビルドします。mvn clean package コマンドは、プロジェクトをクリーンにし、パッケージをビルドします。
FROM debian:12-slim 実行用のコンテナーのベースイメージとして、debian 12 の Slim 版を使用します。
WORKDIR /app 作業ディレクトリを /app に設定します。
RUN apt-get update && apt-get install -y --no-install-recommends openjdk-17-jre-headless && apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* Java 17 の JRE (ヘッドレス版) をインストールします。--no-install-recommends オプションで推奨パッケージをインストールしないことを指定します。その後、不要なファイルをクリーンアップしてイメージサイズを最小限にします。
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/jre 環境変数 JAVA_HOME を設定します。これはJavaのインストールディレクトリを示します。
ENV PATH=$PATH:$JAVA_HOME/bin PATH 環境変数に Java の実行可能ファイルパスを追加します。
COPY --from=build-env /app/target/*.jar app.jar build-env ステージでビルドされた JAR ファイルを、実行用のコンテナー内の作業ディレクトリにコピーします。
EXPOSE 8080 コンテナーがポート 8080 を外部に公開することを示します。
ENTRYPOINT ["java","-jar","app.jar"] コンテナー起動時に実行されるコマンドを指定します。JAR ファイルを Java で実行することで、アプリケーションが実行されます。

Docker デーモンを起動します。

$ sudo service docker start

Docker 環境をお持ちでない場合は、以下の関連記事から Docker Engine のインストール手順をご確認いただけます。

コンテナーイメージをビルドします。

$ docker build \
    --no-cache \
    --tag api-todo-spring-boot:latest .

コンテナーイメージを確認します。

$ docker images | grep api-todo-spring-boot
api-todo-spring-boot    latest    2ac3a8c681cd    17 seconds ago    340MB

ここまでの手順で、ローカル環境の Docker にアプリのカスタムコンテナーイメージをビルドすることができました。

コンテナーを起動

ローカルでコンテナーを起動します。
※ コンテナーを停止するときは ctrl + C を押します。

コンテナー間通信するために net-todo という Docker ネットワークをあらかじめ作成しています。ご注意ください。

$ docker run --rm \
    --publish 5000:8080 \
    --name api-local \
    --net net-todo \
    --env DB_HOST=mysql-todo \
    --env DB_PORT=3306 \
    --env DB_NAME=db_todo \
    --env DB_USER=root \
    --env DB_PASSWORD=password \
    api-todo-spring-boot

ここまでの手順で、ローカル環境の Docker でアプリのカスタムコンテナーを起動することができました。

コンテナーの動作確認

別ターミナルから curl コマンドで確認します。
※ ID で ToDo アイテムを取得します。

$ curl -s http://localhost:5000/todos/8 | jq '.'

レスポンス

{
  "id": "8",
  "content": "コードを書く",
  "created_date": "2024-08-11T11:02:02.000+00:00",
  "completed_date": null
}

ここまでの手順で、ターミナルにレスポンスが表示され、JSON データを取得することができました。

コンテナーの状態を確認してみます。

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                   CREATED          STATUS             PORTS                                                  NAMES
f3641763145b   api-todo-spring-boot   "java -jar app.jar"       37 seconds ago   Up 37 seconds      0.0.0.0:5000->8080/tcp, :::5000->8080/tcp              api-local
5eb600ed3d54   mysql-base             "docker-entrypoint.s…"   16 hours ago     Up About an hour   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql-todo

コンテナーに接続

別ターミナルからコンテナーに接続します。

$ docker exec -it api-local /bin/bash

コンテナー接続後にディレクトリを確認します。
※ コンテナーから出るときは ctrl + D を押します。

# pwd
/app
# ls -lah
total 40M
drwxr-xr-x 1 root root 4.0K Aug 12 03:25 .
drwxr-xr-x 1 root root 4.0K Aug 12 03:26 ..
-rw-r--r-- 1 root root  40M Aug 12 03:25 app.jar

top コマンドで状況を確認します。

# apt update
# apt install procps
# top
top - 03:28:59 up  1:43,  0 user,  load average: 0.05, 0.08, 0.03
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
MiB Mem :  15949.2 total,  12745.8 free,   1812.8 used,   1689.1 buff/cache
MiB Swap:   4096.0 total,   4096.0 free,      0.0 used.  14136.4 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
      1 root      20   0 9082716 362312  22892 S   0.3   2.2   0:15.57 java
     61 root      20   0    4188   3480   2972 S   0.0   0.0   0:00.01 bash
    251 root      20   0    8560   4628   2756 R   0.0   0.0   0:00.00 top

コンテナーの情報を表示してみます。

# cat /etc/*-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

このコンテナーは Debian GNU/Linux をベースに作成されています。つまり、Debian GNU/Linux と同じように扱うことができます。

おまけ:Swagger 装備

ライブラリの追加

springdoc-openapi ライブラリを pom.xml に追記します。

$ vim pom.xml

ファイルの内容

pom.xml ※抜粋
<!-- 省略 -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
+   <dependency>
+       <groupId>org.springdoc</groupId>
+       <artifactId>springdoc-openapi-ui</artifactId>
+       <version>1.7.0</version>
+   </dependency>
    <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>javax.persistence-api</artifactId>
        <version>2.2</version>
    </dependency>
<!-- 省略 -->

Swagger を確認

Web ブラウザで Swagger の URL を確認します。

http://localhost:5000/swagger-ui/index.html

image.png
image.png

これらのツールを活用することにより、より手軽に API を操作できます。

まとめ

WSL Ubuntu の Docker 環境で、Java Spring Boot の最小 API を実装することができました。

この記事の実装例は1つのアプローチに過ぎず、必ずしも正しい方法とは限りません。他にも多様な方法がありますので、さまざまな情報を照らし合わせて検討してみてください。

どうでしたか? WSL Ubuntu で、Java Spring Boot Web アプリケーションを手軽に起動できます。ぜひお試しください。今後も Spring の開発環境などを紹介しますので、ぜひお楽しみにしてください。

推奨コンテンツ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?