Docker 環境と Java Spring Boot で最小 API を実装する:MySQL
こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 24.04 で Java Spring Boot Web アプリケーションを作成し、最小限の REST API を実装する方法を紹介します。
目的
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 クラスの作成
このシリーズの記事で、RDBMS と NoSQL の両方で同じ API 操作を行います。そのため、ID フィールドは数値型ではなく文字列として定義しています。統一性を保ちつつ異なるデータストアで動作することを目指しています。ご了承ください。
Todo エンティティクラスを作成します。
$ mkdir -p src/main/java/com/example/springboot/model
$ vim src/main/java/com/example/springboot/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
ファイルの内容
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
ファイルの内容
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
ファイルの内容
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
ファイルの内容
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
ファイルの内容
<?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
ファイルの内容
# 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
ファイルの内容
<!-- 省略 -->
<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
これらのツールを活用することにより、より手軽に API を操作できます。
まとめ
WSL Ubuntu の Docker 環境で、Java Spring Boot の最小 API を実装することができました。
この記事の実装例は1つのアプローチに過ぎず、必ずしも正しい方法とは限りません。他にも多様な方法がありますので、さまざまな情報を照らし合わせて検討してみてください。
どうでしたか? WSL Ubuntu で、Java Spring Boot Web アプリケーションを手軽に起動できます。ぜひお試しください。今後も Spring の開発環境などを紹介しますので、ぜひお楽しみにしてください。
推奨コンテンツ