💫

DevContainer上のSpringBootからMySQLへアクセス

2024/05/31に公開

はじめに

環境構築のはじめの一歩としてとりあえず動作するものをつくることが目的です。

  • Spring boot を使用した環境構築で、JPA と MySQL を使ってデータベースアクセスを行う
  • Spring 環境はコンテナ上に作成する
  • ソースコードや手順は公式のJPA で MySQL データアクセスをベースとする

JPA とは...

JPA(Java Persistence API)とは、Java で標準的に利用される API の一種で、データの永続的な保管や取り出しを容易にする機能を提供するもの。リレーショナルデータベース(RDB)へのアクセスに用いられることが多い。( e-Words より引用

環境情報

以下のインストール済んでいることが前提条件です。
OS は Windows11 を使用してます。

構成イメージ

ざっくりイメージでこんな感じで構成します。
コンテナに乗っている Spring からホストの Windows 上にある MySQL Server にアクセスします。

全体の流れ

  1. VSCode に Spring Boot Extension Pack を追加し開発用のコンテナを構築する
  2. 開発用コンテナで Spring Initializr を呼び出し、依存関係で以下を選択する  ★①
    • Spring Web
    • Spring Data JPA
    • MySQL ドライバー
  3. 公式を参照しデータベース作成とソースコード作成を行う
  4. ソースコードを修正する  ★②
    • application.properties
    • @Entity モデル(User.java)
  5. アプリケーションをテスト  ★③

① 開発用コンテナで Spring Initializr

  1. コンテナに接続されていることを確認
  2. Ctrl+Shift+P でコマンドパレットを呼び出し、「spring init」と入力すると Spring Initializr が候補にでます。ここでは「Create a Gradle project...」を選択します。
  3. Spring Boot version:3.3.0
  4. project language:Java
  5. Group Id:com.example(デフォルト)
  6. Artifact Id:accessingdatamysql
  7. packing type:Jar
  8. Java version:17(とりあえず公式に合わせただけ)
  9. Choose dependencies
    • Spring Web
    • Spring Data JPA
    • MySQL Driver

以降、参考用のスクショ

② ソースコードを修正

公式ガイドラインに従ってソースコードが用意できたことが前提です。

@Entity モデルの修正

対象:src/main/java/com/example/accessingdatamysql/User.java

ID の採番がうまくいかなかったため、「GenerationType.AUTO」を「GenerationType.IDENTITY」に修正します。

User.java
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」を指定します。

application.properties
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 を修正する必要があります。

  1. VSCode 左下の「開発コンテナー:Java」をクリックするとメニューが出るので、そこから「コンテナー構成ファイルを開く」を選択。

  2. fowardPorts を追加する

devcontainars.json
	"forwardPorts": [
		8080
	]

関連サイト

  • VSCode + DevContainers の構築記事

https://zenn.dev/sleepwalk/articles/7518fbd39043c9

  • 公式ガイドライン

https://spring.pleiades.io/guides/gs/accessing-data-mysql

  • MySQL のダウンロードページ

https://dev.mysql.com/downloads/installer/

Discussion