【IntelliJ+Docker】Spring Boot+Java15+MySQL環境構築

Java

Docker で Spring Boot(Gradle) + MySQL の開発環境を構築する手順について解説します。

また、チュートリアルとして、データベースからユーザーの一覧を取得し一覧画面へ表示する簡単なWebアプリケーションをあわせて作成していきます。

IDEは IntelliJ IDEA Community Edition、Java は2020年9月にリリースされたJava 15を使っていきます。

この記事の内容
  • Docker Compose を使って、アプリケーションサーバーとデータベースサーバーを構築します。
  • IntelliJ IDEA からアプリケーションのホットデプロイとリモートデバックを可能にします。
  • チュートリアルとして、Spring Boot と MySQL を連携した簡単なWebアプリケーションを作成します。

【IntelliJ+Docker】Spring Boot+Java15+MySQL環境

OSmacOS Catalina10.15.6
IDEIntelliJ IDEA Community Edition2020.2.2
DockerDocker Desktop for Mac2.4.0.0
Docker Engine19.03.13
Docker Compose1.27.4
アプリケーションサーバーubuntu20.10
Spring Boot2.3.4
Java(OpenJDK)15
データベースサーバーMySQL5.7.31

ホスト環境構築

Docker Desktop for Mac のインストール

dockerhub へアクセスして、「Docker Desktop for Mac」をダウンロード&インストールしてください。

IntelliJ IDEA Community Edition のインストール

IntelliJ IDEA へアクセスして「Community」をダウンロード&インストールしてください。

IntelliJ IDEA へ Spring Boot の Gradle プロジェクトを作成する

IntelliJ IDEA へ Spring Boot のプロジェクトを作成していきますが、「IntelliJ IDEA Community Edition」 では、Spring Boot のプロジェクトが作成できないため、Spring Initializr を使って Spring Boot のプロジェクトを作成します。

Spring Initializr へアクセスして画像のとおり設定をします。

Dependencies には以下を追加してください。

  • Spring Boot DevTools
  • Spring Web
  • Spring Data JPA 
  • Spring Data JDBC
  • MySQL Driver
  • Thymeleaf
  • Lombok

設定後、下部の GENERATE をクリックして、ダウンロードしたzipファイルを解凍しておきます。

Spring Initializrにてプロジェクトを作成します。

IntelliJ IDEA Community Edition を起動します。
「Open or Import」をクリックして、先ほど解凍したフォルダを開きます。

IntelliJ IDEA Community Edition を起動します。

プロジェクトはこのようになっています。

build.gradle

build.gradle

plugins {
	id 'org.springframework.boot' version '2.3.4.RELEASE'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '15'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'mysql:mysql-connector-java'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

以上で Spring Boot の Gradle プロジェクトが作成できました。

Docker 環境構築

続いて、Docker 環境を構築していきます。

プロジェクトのルートディレクトリに docker を作成します。

├── build.gradle
├── docker
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat

アプリケーションサーバー

docker 配下に java のディレクトリ構造を作成します。

docker/
└── java
    └── Dockerfile

docker / java / Dockerfile

FROM ubuntu:20.10

RUN apt-get update
RUN apt-get -y install openjdk-15-jdk
ENV JAVA_HOME /usr/lib/jvm/java-15-openjdk-amd64
ENV PATH $JAVA_HOME/bin:$PATH
VOLUME /tmp
RUN mkdir /app
WORKDIR /app

データベースサーバー

docker 配下に mysql のディレクトリ構造を作成します。

docker/
├── java
│   └── Dockerfile
└── mysql
    ├── Dockerfile
    ├── conf.d
    │   └── my.cnf
    └── initdb.d
        ├── 1_schema.sql
        └── 2_initdata.sql

docker / mysql / Dockerfile

FROM mysql:5.7

RUN touch /var/log/mysql/mysqld.log

docker / conf.d / my.cnf

[mysqld]
character-set-server=utf8mb4
explicit-defaults-for-timestamp=1
general-log=1
general-log-file=/var/log/mysql/mysqld.log

[client]
default-character-set=utf8mb4

docker / initdb.d / 1_schema.sql

中身は空で良いです。チュートリアルで追記していきます。

docker / initdb.d / 2_initdata.sql

中身は空で良いです。チュートリアルで追記していきます。

docker-compose.yml

docker 配下に docker-compose.yml を作成します。

docker/
├── docker-compose.yml
├── java
│   └── Dockerfile
└── mysql
    ├── Dockerfile
    ├── conf.d
    │   └── my.cnf
    └── initdb.d
        ├── 1_schema.sql
        └── 2_initdata.sql

docker / docker-compose.yml

version: '3.8'
volumes:
  mysql-db:
    driver: local
services:
  db:
    build: ./mysql
    image: demo-db:0.0.1
    restart: always
    volumes:
      - ./mysql/conf.d:/etc/mysql/conf.d
      - ./mysql/initdb.d:/docker-entrypoint-initdb.d
      - ../log/mysql:/var/log/mysql
    expose:
      - 3306
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: demo_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: rootpassword
  app:
    build: ./java
    image: demo-app:0.0.1
    volumes:
      - ../:/app
    ports:
      - "8080:8080"
    depends_on:
      - db
    command: ./gradlew clean bootRun

src/main/resources 配下に application.yml を作成します。

src/main/resources/application.yml

spring:
  profiles:
    active: develop
---
spring:
  profiles: develop
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://db:3306/demo_db
    username: user
    password: password
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    web:
      base-path: /
---
spring:
  profiles: production
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxxx:3306/demo_db
    username: xxxx
    password: xxxx
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    web:
      base-path: /

build.gradle の末尾に以下のコードを追記します。

build.gradle

bootRun {
	jvmArgs = ["-Dspring.profiles.active=develop"]
}

Docker コンテナの起動と動作確認

ビルドしてDocker イメージを作成する

ターミナルを起動後、docker へ移動して、「docker-compose build」コマンドを実行します。

$ cd docker/
$ docker-compose build

数分待つと、Docker イメージが作成されます。

Building db
Step 1/2 : FROM mysql:5.7
5.7: Pulling from library/mysql
:
省略
:
Successfully built ee5c567b0fc6
Successfully tagged demo-app:0.0.1

Docker コンテナを起動する

「docker-compose up -d」コマンドを実行します。

$ docker-compose up -d

コンテナが起動しました。

Creating network "docker_default" with the default driver
Creating docker_db_1 ... done
Creating docker_app_1 ... done

「docker ps」コマンドを実行して、コンテナが起動していることを確認します。
「docker_app_1」と「docker_db_1」が起動していればOKです。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES
7151b171b959        demo-app:0.0.1      "./gradlew clean boo…"   31 seconds ago      Up 30 seconds       0.0.0.0:8080->8080/tcp              docker_app_1
7adf9cdc3a39        demo-db:0.0.1       "docker-entrypoint.s…"   32 seconds ago      Up 31 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   docker_db_1

コンテナのログは以下のコマンドで確認することができます。
※ -f オプションを付けると tail できます。

$ cd docker/
$ docker-compose logs -f

Docker コンテナに入ってバージョンを確認する

アプリケーションサーバー

コンテナに入る

$ docker exec -it docker_app_1 bash

OS のバージョンを確認する

# cat /etc/os-release 
NAME="Ubuntu"
VERSION="20.10 (Groovy Gorilla)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu Groovy Gorilla (development branch)"
VERSION_ID="20.10"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=groovy
UBUNTU_CODENAME=groovy

Java のバージョンを確認する

# java -version
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-Ubuntu-1)
OpenJDK 64-Bit Server VM (build 15+36-Ubuntu-1, mixed mode, sharing)
# javac -version
javac 15

コンテナから出る

# exit

データベースサーバー

コンテナに入る

$ docker exec -it docker_db_1 bash

MySQL のバージョンを確認する

# mysql --version
mysql  Ver 14.14 Distrib 5.7.31, for Linux (x86_64) using  EditLine wrapper

コンテナから出る

# exit

Docker コンテナを停止&削除する

「docker-compose down」コマンドを実行します。

$ docker-compose down

コンテナが停止した後に削除されました。

Stopping docker_app_1 ... done
Stopping docker_db_1  ... done
Removing docker_app_1 ... done
Removing docker_db_1  ... done
Removing network docker_default

「docker ps」コマンドを実行して、コンテナが削除されていることを確認します。
「docker_app_1」と「docker_db_1」が表示されなければOKです。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

アプリケーションの動作確認

DemoApplication.java へ以下の処理を追記します。

src / main / java / DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;  // 追加
import org.springframework.web.bind.annotation.RestController;  // 追加

@SpringBootApplication
@RestController  // 追加
public class DemoApplication {
        // 追加
	@RequestMapping("/")
	public String home() {
		return "Hello World";
	}
        
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

「docker-compose up -d」コマンドでコンテナを起動します。

$ docker-compose up -d

ログを見てアプリケーションが起動したのを確認したら、以下の URL へアクセスします。

http://localhost:8080

「Hello World」が表示されたらアプリケーションの動作確認は完了です。

IntelliJ IDEA のホットデプロイ設定

build.gradle の dependencies に依存パッケージとして「developmentOnly 'org.springframework.boot:spring-boot-devtools'」を追加します。
※既に記載されている場合は追記する必要はありません。

build.gradle

dependencies {
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
}

IntelliJ IDEA -> Preference -> Build, Execution, Deployment -> Compiler で「Build project automatically」にチェックを入れます。

IntelliJ IDEA -> Preference -> Build, Execution, Deployment -> Compiler で「Build project automatically」にチェックを入れます。

SHIFT + CTRL + A で表示されるウィンドウで「Registry」を検索し、 compiler.automake.allow.when.app.running にチェックを入れます。

SHIFT + CTRL + A で表示されるウィンドウで「Registry」を検索し、 compiler.automake.allow.when.app.running にチェックを入れます。

ターミナルでコンテナのログを監視して、ホットデプロイの設定を確認していきます。

$ docker-compose logs -f

コードを追記してファイルを保存した後にログが追記されたことを確認したらホットデプロイの設定は完了です。

IntelliJ IDEA のリモートデバック設定

IntelliJ IDEA のメニュー -> Run -> Edit Configurations を開きます。

IntelliJ IDEA のメニュー -> Run -> Edit Configurations を開きます。

左上の + ボタンから Remote を選択します。
そのまま「OK」をクリックします。
※ Command line arguments for remote JVM のコマンドをコピーしてください。build.gradle に追記します。

左上の + ボタンから Remote を選択します。
そのまま「OK」をクリックします。
※ Command line arguments for remote JVM のコマンドをコピーしてください。build.gradle に追記します。

build.gradle の bootRun へ追記します。

build.gradle

bootRun {
	jvmArgs = [
			"-Dspring.profiles.active=develop",
			"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
			]
}

docker-compose.yml にリモートデバック用のポート「5005」を追記します。

docker / docker-compose.yml

app:
  ports:
    - "8080:8080"
    - "5005:5005"

コンテナを停止&起動します。

$ docker-compose down
$ docker-compose up -d

別のターミナルでコンテナの起動ログを監視します。

$ docker-compose logs -f

ログに「Listening for transport dt_socket at address: 5005」が表示されます。

app_1  | > Task :bootRun
app_1  | Listening for transport dt_socket at address: 5005
app_1  | 
app_1  |   .   ____          _            __ _ _
app_1  |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
app_1  | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
app_1  |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
app_1  |   '  |____| .__|_| |_|_| |_\__, | / / / /
app_1  |  =========|_|==============|___/=/_/_/_/

DemoApplication.java へブレークポイントを配置します。

src / main / java / DemoApplication.java

DemoApplication.java へブレークポイントを配置します。

IntelliJ IDEA の右上にあるデバックツールからリモートデバックを開始します。

IntelliJ IDEA の右上にあるデバックツールからリモートデバックを開始します。

コンソールに「Connected to the target VM, address: 'localhost:5005', transport: 'socket'」が表示されます。

コンソールに「Connected to the target VM, address: 'localhost:5005', transport: 'socket'」が表示されます。
http://localhost:8080 へアクセスします。

ブレークポイントの位置で処理が止まってデバックをすることができます。

ブレークポイントの位置で処理が止まってデバックをすることができます。

以上でリモートデバックの設定は完了です。

チュートリアル

続いて、データベースからユーザーの一覧を取得して一覧画面へ表示する簡単なWebアプリケーションを作成していきます。

データベース

データベースへテーブルを作成しデータを登録していきます。

テーブル作成の SQL を記載します。

docker / initdb.d / 1_schema.sql

CREATE TABLE users (
    id INT NOT NULL AUTO_INCREMENT,
    first_name varchar(20),
    last_name varchar(20),
    PRIMARY KEY (id)
);

データ作成の SQL を記載します。

docker / initdb.d / 2_initdata.sql

INSERT INTO users(first_name, last_name) VALUES('Ichiro', 'Suzuki');
INSERT INTO users(first_name, last_name) VALUES('Taro', 'Tanaka');

コンテナを停止&起動します。(1_schema.sql、2_initdata.sql がコンテナ起動時に実行されます)

$ docker-compose down
$ docker-compose up -d

コンテナが起動したことを確認したら、データベースを確認します。

$ docker exec -it docker_db_1 bash
# mysql -u user -ppassword demo_db
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| demo_db            |
+--------------------+
2 rows in set (0.00 sec)

mysql> show tables;
+-------------------+
| Tables_in_demo_db |
+-------------------+
| users             |
+-------------------+
1 row in set (0.01 sec)

mysql> select * from users;
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | Ichiro     | Suzuki    |
|  2 | Taro       | Tanaka    |
+----+------------+-----------+
2 rows in set (0.00 sec)

テーブルとデータが作成されていればOKです。

アプリケーション

先ほど作成したデータベースのテーブルからデータを取得し、一覧画面に表示するWEBアプリケーションを作成していきます。

最終的にこのような構造になります。

src/
├── main
│   ├── java
│   │   └── com
│   │       └── example
│   │           └── demo
│   │               ├── DemoApplication.java
│   │               ├── controller
│   │               │   └── UserController.java
│   │               ├── domain
│   │               │   └── User.java
│   │               ├── repository
│   │               │   └── UserRepository.java
│   │               └── service
│   │                   └── UserService.java
│   └── resources
│       ├── application.properties
│       ├── application.yml
│       ├── static
│       └── templates
│           └── user
│               └── list.html

src / main / java / com.example.demo / domain / User.java

package com.example.demo.domain;

import lombok.Data;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;

@Data
@Entity
@Table(name = "users")
public class User implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Integer id;
    private String firstName;
    private String lastName;
}

src / main / java / com.example.demo / repository / UserRepository.java

package com.example.demo.repository;

import com.example.demo.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}

src / main / java / com.example.demo / service / UserService.java

package com.example.demo.service;

import com.example.demo.domain.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class UserService {
    @Autowired
    UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }
}

src / main / java / com.example.demo / controller / UserController.java

package com.example.demo.controller;

import com.example.demo.domain.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("users")
public class UserController {
    @Autowired
    UserService usrService;

    @GetMapping(path = "")
    String list(Model model) {
        List<User> users = usrService.findAll();
        model.addAttribute("users", users);
        return "user/list";
    }
}

src / main / resources / templates / user / list.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Users</title>
    </head>
    <body>
        <table>
            <thead>
                <tr>
                    <th>id</th>
                    <th>firstname</th>
                    <th>lastname</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="user : ${users}">
                    <td th:text="${user.id}">id</td>
                    <td th:text="${user.firstName}">firstName</td>
                    <td th:text="${user.lastName}">lastName</td>
                </tr>
            </tbody>
        </table>
    </body>
</html>

コンテナを停止&起動します。

$ docker-compose down
$ docker-compose up -d
コンテナが起動したら http://localhost:8080/users へアクセスします。

以下のようにユーザー一覧が表示されたら完成です。

ユーザー一覧が表示されたら完成です。

静的リソースのホットデプロイ

現状では、Thymeleafのテンプレートの変更が反映されずにアプリケーションサーバーの再起動が必要になり開発効率が悪いので、静的リソースもホットデプロイ可能にしていきます。

build.gradle へ「sourceResources sourceSets.main」を追記します。

build.gradle

bootRun {
	sourceResources sourceSets.main
	jvmArgs = [
			"-Dspring.profiles.active=develop",
			"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
			]
}

application.yml へ「spring.thymeleaf.cache」を追記します。
※deveop (開発)は「false」、production (本番)は「true」を設定し環境に応じてキャッシュの設定を切り替えます。

src / resources / application.yml

spring:
  profiles:
    active: develop
---
spring:
  profiles: develop
  thymeleaf:
    cache: false
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://db:3306/demo_db
    username: user
    password: password
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    web:
      base-path: /
---
spring:
  profiles: production
  thymeleaf:
    cache: true
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://xxxx:3306/demo_db
    username: xxxx
    password: xxxx
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      ddl-auto: update
  data:
    web:
      base-path: /

コンテナを停止&起動します。

$ docker-compose down
$ docker-compose up -d
コンテナが起動したら http://localhost:8080/users へアクセスします。

HTMLを変更してブラウザをリロードしてください。

変更が反映されていると思います。

コメント

タイトルとURLをコピーしました