DevContainer上のSpringBootからMySQLへアクセス
はじめに
環境構築のはじめの一歩としてとりあえず動作するものをつくることが目的です。
- Spring boot を使用した環境構築で、JPA と MySQL を使ってデータベースアクセスを行う
- Spring 環境はコンテナ上に作成する
- ソースコードや手順は公式のJPA で MySQL データアクセスをベースとする
JPA とは...
JPA(Java Persistence API)とは、Java で標準的に利用される API の一種で、データの永続的な保管や取り出しを容易にする機能を提供するもの。リレーショナルデータベース(RDB)へのアクセスに用いられることが多い。( e-Words より引用)
環境情報
以下のインストール済んでいることが前提条件です。
OS は Windows11 を使用してます。
- Visual Studio Code
- Docker Desktop
- MySQL Server 8.0
- MySQL が動いていることが前提で書いてますのでダウンロードがまだならこちら
- MySQL Community Server ダウンロード先
構成イメージ
ざっくりイメージでこんな感じで構成します。
コンテナに乗っている Spring からホストの Windows 上にある MySQL Server にアクセスします。
全体の流れ
- VSCode に Spring Boot Extension Pack を追加し開発用のコンテナを構築する
- 開発用コンテナで Spring Initializr を呼び出し、依存関係で以下を選択する ★①
- Spring Web
- Spring Data JPA
- MySQL ドライバー
- 公式を参照しデータベース作成とソースコード作成を行う
-
https://spring.pleiades.io/guides/gs/accessing-data-mysql
- データベースを作成する
- application.properties ファイルを作成する
- @Entity モデルを作成する
- リポジトリを作成する
- コントローラーの作成
-
https://spring.pleiades.io/guides/gs/accessing-data-mysql
- ソースコードを修正する ★②
- application.properties
- @Entity モデル(User.java)
- アプリケーションをテスト ★③
① 開発用コンテナで Spring Initializr
- コンテナに接続されていることを確認
- Ctrl+Shift+P でコマンドパレットを呼び出し、「spring init」と入力すると Spring Initializr が候補にでます。ここでは「Create a Gradle project...」を選択します。
- Spring Boot version:3.3.0
- project language:Java
- Group Id:com.example(デフォルト)
- Artifact Id:accessingdatamysql
- packing type:Jar
- Java version:17(とりあえず公式に合わせただけ)
- Choose dependencies
- Spring Web
- Spring Data JPA
- MySQL Driver
以降、参考用のスクショ
② ソースコードを修正
公式ガイドラインに従ってソースコードが用意できたことが前提です。
@Entity モデルの修正
対象:src/main/java/com/example/accessingdatamysql/User.java
ID の採番がうまくいかなかったため、「GenerationType.AUTO」を「GenerationType.IDENTITY」に修正します。
package com.example.accessingdatamysql;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity // This tells Hibernate to make a table out of this class
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) // ★AUTO → IDENTITYに修正
private Integer id;
private String name;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
application.properties の修正
対象:src/main/resources/application.properties
Docker からホスト(Windows11)の MySQL にアクセスするため「host.docker.internal」を指定します。
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://host.docker.internal:3306/db_example # ★host.docker.internal に指定
spring.datasource.username=springuser
spring.datasource.password=ThePassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
③ アプリケーションをテスト
VSCode のターミナルで gradew コマンドを投入しアプリケーションを起動します
- GET localhost:8080/demo/all: すべてのデータを取得します。
- POST localhost:8080/demo/add: 1 人のユーザーをデータに追加します。
./gradlew bootRun
VSCode のターミナルから 1 つ bash を起動し curl コマンドをアプリケーションに投げます。
curl http://localhost:8080/demo/add -d name=First -d email=someemail@someemailprovider.com
データの登録を終えたので、下記の curl でテーブルにあるユーザデータを表示します。
curl http://localhost:8080/demo/all
# レスポンス
[{"id":1,"name":"First","email":"someemail@someemailprovider.com"}]
ホスト(Windows11)の PowerShell から curl コマンドを叩く場合は、devcontainars.json を修正する必要があります。
うまくいかなかったところ
JPA からの保存でエラー
JPA で save()するときに以下のエラーメッセージが発生。
java.sql.SQLSyntaxErrorException: Table 'db_example.user_seq' doesn't exist
AUTO でできない理由まで調べなかったけれど、
今回は「GenerationType.AUTO」を「GenerationType.IDENTITY」に修正することでエラー回避
コンテナ上のアプリからホストの MySQL に接続できない(1)
application.properties の spring.datasource.url が正しくなかったときに遭遇したエラー。
コンテナ上のアプリからホスト OS にアクセスするために「host.docker.internal」を指定することで解決。
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
~中略~
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[na:na]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:104) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:149) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:165) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:88) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.NativeSession.connect(NativeSession.java:120) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:935) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:805) ~[mysql-connector-j-8.3.0.jar:8.3.0]
... 49 common frames omitted
Caused by: java.net.ConnectException: Connection refused
at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:542) ~[na:na]
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:592) ~[na:na]
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[na:na]
at java.base/java.net.Socket.connect(Socket.java:751) ~[na:na]
at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:153) ~[mysql-connector-j-8.3.0.jar:8.3.0]
at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:62) ~[mysql-connector-j-8.3.0.jar:8.3.0]
... 52 common frames omitted
java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(java.sql.SQLException, String)" because the return value of "org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.sqlExceptionHelper()" is null
コンテナ上のアプリからホストの MySQL に接続できない(2)
application.properties でhost.docker.internalを指定したにも関わらず接続できなかった。
結論として、cleanするだけで解決するあるあるだった。
./gradlew clean
ホスト(Windows11)の PowerShell から コンテナ上のアプリへ curl コマンドを叩けない
ホスト(Windows11)の PowerShell から curl コマンドを叩く場合は、devcontainars.json を修正する必要があります。
-
VSCode 左下の「開発コンテナー:Java」をクリックするとメニューが出るので、そこから「コンテナー構成ファイルを開く」を選択。
-
fowardPorts を追加する
"forwardPorts": [
8080
]
関連サイト
- VSCode + DevContainers の構築記事
- 公式ガイドライン
- MySQL のダウンロードページ
Discussion