Java(SpringBoot)+ Eclipceのはじめかた

- JavaはJava言語とJVMの総称である。
- Javaのプログラムを開発するにはJDKが必要になるが、この開発を担っているのはOpenJDKプロジェクトであるとのこと。
- このOpenJDKをもとにいろいろなベンダーが実行可能なパッケージとしてディストリビューションを配布している。

何をインストールすれば良いのか
- JDK(Java Development Kit)をダウンロードしてインストールする。
- Javaの仕様は複数存在するがSE(Standard Edition)が名前の通り最も標準
- ディストリビューションも多数存在するが、何も考えずに「JDK インストール」と検索して出てくるのはOracle製のJDK
JDKのバージョン
- ディストリビューションは多数存在するが、バージョンに用いられる数字は標準化されている模様。
- Javaの17 と JDKの17は同じ言語仕様のJavaを扱うと考えて問題ない。
- 現行でどのバージョンを選べば良いかについては、多くで長期サポートのバージョン(LTS)が推薦されており、以下の記事を見ると現在は17か21にしておくのが良さそう。
- 8→11のバージョンアップはきついと多くで言われているのを見かける。

SpringBootでWebアプリを作成する
これまで他言語を学習してきた中で感じているのは、
入門書を愚直に1からやっていくのは退屈で、
Webアプリケーション等作りながら手を動かしてやっていく方が性に合っている気がする。

IDEの選定
JavaでメジャーなフレームワークはSpirng Framwork、SpringBootあたりであるが、Springアプリケーションの開発コミュニティからSTS(Spring Tool Suite)というIDEがある。これはEclipceベースにSpring開発に必要なものを足したものなので、Eclipceは内含されている。
もう1つ、JavaのIDEでメジャーなものにEclipceが存在するが、これもEclipceのマーケットプレイスからSTSのプラグインを導入しているのであればSTSに近い環境を用意することができる模様。
SpringとSpring Framework 、SpringBootの関係
Javaの文脈でSpringといったときはSpring Frameworkを指すっぽい。
Spring Frameworkという名前を聞いた時に「何かのフレームワークなんだな」と思うが、これが関係の理解をややこしくしていると思う。
Spring Frameworkはアプリケーション開発に必要なフレームワーク(モジュール)の集合体であり、一部具体例としてはSpring Security、Spring MVCというモジュールを内含している。
SpringBootは設定が複雑なSpringFramworkの簡易版として紹介されることが多いが、SpringFramworkのSpringMVCがベースとなっている。
BootもSpringFramworkというフレームワークの集合体の一部として位置付けられており、派生して独立したという扱いでもないらしい。
Spring bootとは?Spring frameworkとの違いは?
STSバージョン3までは「ファイル」->「新規」に「Spring Legacy Project」というメニューがあり、そこからSpringFramwork(SpringMVC)を扱うことができたらしいが、STS4からはこのメニューは廃止されてしまった模様。
Spring tool suiteのSpring legacy project選択時、ファイルが自動生成されない
ただ、またややこしいのがSpringFramwork自体の開発が終了した訳ではなく、2022年にバージョン6がリリースされている。

Tomcatとは
正式にはApache Tomcat。Wikipediaの引用は以下の通り。
Apache Tomcat(アパッチ トムキャット)は、Java ServletやJavaServer Pages (JSP) を実行するためのWebコンテナ(サーブレットコンテナ、サーブレットエンジン)である。Apache License 2.0を採用したオープンソースソフトウェア。
Apache HTTP Serverとの違い
Tomcatという名称だけは聞いたことあるが、Apacheが開発していたとは知らなかった。
PHPerとしてはApacheと聞くとApache HTTP Serverの方をイメージする。
いまさら聞けない「Apache HTTP Server」と「Apache Tomcat」の違いとは?
Apache HTTP ServerはWebサーバーであり、ApacheTomcatはアプリケーションサーバーであると、そもそもの性質が異なる。
ウェブサーバーとアプリケーションサーバーの違い - テクノロジーサーバーの違い - AWS
超平たく言うとWebサーバーはHTTPやCSS、JavaScriptなどの静的コンテンツを配信、アプリケーションサーバーは動的なコンテンツを配信する。
HTTP ServerがPHPなどの動的なコンテンツを配信できるのはモジュールによってアプリケーションサーバーに相当する機能が拡張されているからである。
また、Wikiでの説明にはコンテナという単語が出てきたが、Dockerのコンテナとは関係ない。
Apache Tomcatとは何なのか?
TomcatはJava ServletやJSP(JavaServer Pages)を実行するためのサーブレットコンテナであるとWikiでは説明されていた。
Java ServletやJSPとはサーバー上で実際に動かすプログラムのことで、Zip形式のWARファイル化したものををTomcatにデプロイすることでアプリケーションとして実行することができる。
さらに上記のTomcatやアプリケーションサーバーはJVMと呼ばれるJava仮想マシン上で動いている。
Difference between the Apache HTTP Server and Apache Tomcat? - Stack Overflow

今回はWebAPIでURL叩くとHello World返すところまで作成して、AWSなりGCPにデプロイするところまでやってみたい。
EclipceでSpringBootの新規プロジェクト作成
Eclipceのメニューバーから「ファイル」->「新規」-> 「Spring スターター・プロジェクト」を選択する。
JarとWarの選択については、WebAPI想定なのでWarを選択することにした。
それ以外はデフォルトの設定を適用(Javaのバージョンは21、ビルドツールはGradle)し、オプションにはMySQLドライバーを追加した。
JarとWarの違い
Jar(Java Archive)はスタンドアロンなアプリケーション(ローカルマシンで実行するようなアプリケーション)の作成を想定、War(Web Archive)はWebアプリケーションの作成を想定しているとのこと。
分かりやすいところで言えばJarにはサーバー(組み込みのTomcat)が含まれており、Warの場合は本番環境等で別のサーバーが必要になる。
SpringBootの初期ディレクトリ
├─ gradle
│ └─ wrapper
│ ├─ gradle-wrapper.jar
│ └─ gradle-wrapper.properties
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ └─ [your base package]
│ │ └─ resources
│ │ ├─ static
│ │ ├─ templates
│ │ └─ application.properties
│ └─ test
│ ├─ java
│ │ └─ [your base package]
│ └─ resources
├─ .gitignore
├─ build.gradle
├─ gradlew
├─ gradlew.bat
└─ settings.gradle
(上記のディレクトリ図はchatGPTに出してもらった)。
Githubの公開リポジトリでSpringBootのプロジェクトで一番上のmacrozheng/mallを見ると、mainディレクトリにcontrollerやservice、configなど役割ごとにディレクトリを切ってソースコードが格納されている。
パッケージの作成
先ほどのcontrollerやservice、configなどのディレクトリ「パッケージ」という概念で区切られている。
パッケージと聞くと「ある特定の機能を提供するためのファイル群」、つまりライブラリやモジュールが想起される(実際にSwiftとかのパッケージの定義はそう)が、Javaでいうパッケージはクラス名衝突を避けるための名前空間を提供する機能である。
プロジェクト作成時のセットアップ画面にもパッケージを指定する欄があり、デフォルトの「com.examle.demo」のまま作成すると「src/main/java/com/example/demo」と「.」で区切ったディレクトリ階層が生成されている。
macrozheng/mallではdemoディレクトリ以下にパッケージが並んでいるので、今回はこれに倣ってパッケージを作成したい。
こちらに
SpringBootでHelloWorldを動かす環境を用意する
Dockerを使ってSpringBootの開発環境を用意していきたい。
アプリケーションのコンテナ(SpringBootを動かすコンテナ)については、Dockerfileに書くべき内容が分かっていないので、最初はMySQLコンテナだけ立てて後から拡張していく。

Hello Worldを返すRestAPIを作成する
ルール
- ファイル名とクラス名は同一にする必要がある
- packageキーワードを使って名前空間を明示する必要がある
- RestControllerをインポート

パッケージを作成
ファイルメニューからパッケージの作成。
コントローラクラスを作成
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello World";
}
}
HelloController.javaファイルを作成し、上記のように記述。

githubにコミットしてみる
準備
EclipceにEGit(統合Git)がインストールされているか確認
ローカルリポジトリの初期化
-
プロジェクトエクスプローラからローカルリポジトリを初期化したいプロジェクトを右クリックで選択し、「チーム」->「プロジェクトの共用」と選択
-
「プロジェクトの親フォルダー内のリポジトリーを使用または作成」にチェックを入れる。
ここで「Eclipse ワークスペースにリポジトリーを作成することはお勧めしません」と警告が出て先に進めず「!?!?!?」ってなったが、画面下半分にある「リポジトリの作成」というボタンを押せばプロジェクトルートに.git
ディレクトリが作成される。
- 「完了」を押す。
ファーストコミットを行う
-
再びプロジェクトを右クリックして、「チーム」内のオプションを見ると先ほどと表示項目が変わっている。「コミット」を選択。
-
リストよりも「プレゼンテーション」->「ツリー表示」に切り替えた方が見やすいと思う。
-
コミットしたい変更をステージし、メッセージを入れてコミットを実行する。
リモートリポジトリにpush
-
リモートリポジトリ(今回はgithub上に)を作成
-
eclipce上で「チーム」->「ブランチのpush'main'」を選択
※デフォルトのmasterブランチからmainブランチに変更しています。
-
以下のようにssh接続先を記載
ユーザー名とパスワードは不要で、使用しているPCで定義されたsshの接続先が使用される。
※自分は複数のリモートリポジトリの設定を同居させていた時期があり、/.ssh/configにホスト名104devとしてgithub.comとは別の設定構成を立てています。
-
以下のように「新規ブランチ」と表示されていることを確認し、プッシュを実行。

EclipceのエクスプローラとVSCodeとの使い勝手の違い
- EclipceにはVSCodeのようなファイルエクスプローラは存在しない。
- ワークスペースを指定して、その直下に任意のプロジェクトを配置して運用する前提っぽい。
- 上メニューから「ファイル」->「ワークスペースの切り替え」でSpringBootのプロジェクトのルートを指定したが、プロジェクト単位での認識がされていないためか、プロジェクトエクスプローラには以下のように何も表示されなかった。

自動補完の設定
- VSCodeの場合は言語専用の拡張機能をインストールすることによって補完機能が強化されるが、Eclipceの場合は設定から補完を効かせる先頭の文字を定義しなくてはならない。
- 「設定」->「Java」->「エディター」-> 「コンテンツアシスト」を開いて、Javaの有効化トリガーに全てのアルファベットとアンダースコアを定義する。

DB接続設定
DockerでDBのコンテナを立てる
version: "3"
services:
mysql:
platform: linux/x86_64
image: mysql:8
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app_db
MYSQL_USER: app_db
MYSQL_PASSWORD: secret
TZ: "Asia/Tokyo"
volumes:
- db-data:/var/lib/mysql
ports:
- 3306:3306
volumes:
db-data:

接続先情報を記載
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/app_db
spring.datasource.username=app_db
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.jpa.show-sql: true

flywayでマイグレーションを行う
1. build.gradleに依存関係を追加
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.flywaydb:flyway-core' //追記
}
- gradleではflywayが何故か正しく動作してくれなかったので、mavenでプロジェクトを作り直し。
pom.xmlのdependenciesにflyway-coreと今回はMySQLなのでflyway-mysqlを追記。pluginにもflyway関連のものを追記。
<dependencies>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<url>jdbc:mysql://localhost:3306/app_db</url>
<user>app_db</user>
<password>secret</password>
<schemas>
<schema>app_db</schema>
</schemas>
</configuration>
</plugin>
</plugins>
</build>
src/main/resources/application.propertiesに以下を追記。
spring.datasource.url=jdbc:mysql://localhost:3306/app_db
spring.datasource.username=app_db
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.flyway.baseline-on-migrate=true
spring.flyway.enabled=true
spring.jpa.hibernate.ddl-auto=update
- src/main/resources/db/migrationディレクトリを作成し、
V{バージョン番号}__{説明}.sql
の命名規則でsqlファイルを配置。とりあえず予約システムっぽいテーブルを作成。
CREATE TABLE IF NOT EXISTS `books` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`begin_dt` DATETIME NOT NULL,
`end_dt` DATETIME NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`last_name` VARCHAR(45) NOT NULL,
`first_name` VARCHAR(45) NOT NULL,
`sex` ENUM('male', 'femail') NULL,
`birth` DATE NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `menus` (
`id` INT NOT NULL,
`name` VARCHAR(45) NULL,
`default_time_slot` INT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `book_menus` (
`id` INT NOT NULL AUTO_INCREMENT,
`book_id` INT NOT NULL,
`menu_id` INT NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB;

- マイグレーションの実行
ターミナルからmaven経由でマイグレーションを実行。
mvnコマンドだとグローバルにmavenが実行できる状態でないと使えないが、プロジェクトルートのmvnwを叩けばそのまま実行できる。
> ./mvnw clean flyway:migrate
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< com.example:jar-demo >------------------------
[INFO] Building jar-demo 0.0.1-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.3.2:clean (default-clean) @ jar-demo ---
[INFO] Deleting /xxx/workspace/jar-demo/target
[INFO]
[INFO] --- flyway:9.22.3:migrate (default-cli) @ jar-demo ---
[INFO] Flyway Community Edition 9.22.3 by Redgate
[INFO] See release notes here: https://rd.gt/416ObMi
[INFO]
[INFO] Database: jdbc:mysql://localhost:3306/app_db (MySQL 8.0)
[INFO] Successfully validated 1 migration (execution time 00:00.033s)
[INFO] Current version of schema `app_db`: 1
[INFO] Schema `app_db` is up to date. No migration necessary.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.087 s
[INFO] Finished at: 2023-12-31T21:22:06+09:00
[INFO] ------------------------------------------------------------------------
gradleの場合
./gradlew flywayMigrate

- SpringBootの実行時に自動的にマイグレーションを走らせる
先ほどのpom.xmlにspring-boot-starter-data-jdbc
の依存関係を追加する。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
あとは普通にSpringBootプロジェクトを立ち上げれば、マイグレーションが走る。
【参考】
java - Spring Boot Application is not running Flyway migrations on startup - Stack Overflow
java - Spring boot flyway migration not being triggered on application startup - Stack Overflow

GAEにデプロイしてみる
コンソールからプロジェクトの作成
コンソールからGCP上でプロジェクトを作成し、
リージョンはus-centralのままでプロジェクト作成

プロジェクト側でやること
- gcloudコマンドを使う場合は、GAE側がソースコードからよしなにビルドしてくれるため、手動でjarファイルの出力は行わなくて良い。
- mavenのコマンドでデプロイを行う場合はpom.xmlにGAE用のプラグインを入れる必要があるが、gcloudコマンドでデプロイする場合は不要。
app.ymlを配置
こちらを参考に/src/main/appengine/
ディレクトリの内にapp.ymlを作成する。
最小インスタンス数を0にしておかないと、ずっとインスタンスが立ち上がって無料枠を超えてしまうので注意する。
runtime: java17
env: standard
instance_class: F1
automatic_scaling:
min_instances: 0
max_instances: 2
min_idle_instances: 0
max_idle_instances: 2
自分がお試しに書いたのは上記のような感じ。
gcloud
> gcloud auth login
> gcloud config set project <projectID>
> gcloud app deploy
...
You can stream logs from the command line by running:
$ gcloud app logs tail -s default
To view your application in the web browser run:
$ gcloud app browse
初めてgcloudのコマンドを使う際にはログインとプロジェクトのセット(切り替え)を行う必要がある。
デプロイが完了したら gcloud app browse
を叩くとブラウザで確認するためのURLを取得できるので、そちらのURLを確認する。
自分は最初503エラーでアクセスできなかったが、どうやらpom.xmlに記載している依存モジュールを上手く処理できなかったようで、エラーが出る場合はGCP管理画面のログ エクスプローラを確認して原因を特定する必要がある。

Docker環境を作ってみる
やることとしては以下のようになる認識
- docker-composeを使う。
- アプリケーションコンテナのベースイメージはJDK。よく使われているのはOpenJDKっぽい。
- あらかじめtargetディレクトリにjarファイルを出力しておいて、コンテナを立ち上げたときにjarファイルをjavaコマンドで実行する。gradleであれば
./gradlew clean build
で、mavenなら./mvnw clean package
を実行。

Dockerfileを書く
FROM openjdk:17
WORKDIR /app
COPY target/your-spring-boot-app.jar /app/app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
オーソドックスなやり方だと上記のような感じ

version: "3"
services:
app:
platform: linux/x86_64
container_name: app
image: maven:3.8.3-openjdk-17
command: mvn spring-boot:run
ports:
- 8080:8080
volumes:
- ./src:/usr/src/spring-project
working_dir: /usr/src/spring-project
.jarファイルを出力していない状態で走らせようとすると、maven(or gradle)が入ったイメージを使って上記のようになる。

DB接続先
eclipceで動かしていたときとdocker-composeで動かすときではDBの接続先に注意する必要がある。
spring.datasource.url=jdbc:mysql://localhost:3306/app_db //eclipce + mysqlコンテナ
spring.datasource.url=jdbc:mysql://mysql:3306/app_db //docker-compose

dev-toolを入れれば実現できるかと思っていた変更を即時に反映するホットロードは、そのままだと厳しそう。
VSCodeの場合は上記手順を踏めばいけるのか。後ほど検証。
gradleとmavenの違いもあるらしくややこしい。
他、後で見る