Bài viết lần này sẽ hướng dẫn các bạn deploy combo ứng dụng Spring Boot web app và Cơ Sở Dữ liệu MySQL lên Docker

1. Giới thiệu:

Trong các bài viết trước về tìm hiểu Docker, chúng ta đã tìm hiểu về các khái niệm chung nhất của Docker như Image, Container là gì và cách tạo ra cũng như ứng dụng chúng.

Chi tiết về tất cả các option về lệnh docker-compose các bạn có thể tham khảo trực tiếp ở trang tài liệu chính thức của Docker: Docker Compose Reference.

Trong giới hạn bài viết sẽ tập trung giới thiệu những command cơ bản và thường dùng của lệnh docker-compose - cũng như hướng dẫn cách deploy Spring Boot web app cùng với MySQL.

Nếu các bạn chưa có khái niệm hoặc chưa tìm hiểu về Docker, các bạn có thể tìm đọc lại các bài viết giới thiệu ở đây: Tìm hiểu Docker - Phần 1Tìm hiểu Docker - Phần 2.

Nhìn một cách rộng hơn chúng ta có thể nhìn thấy rằng, docker-compose dùng để deploy một Hệ thống hoàn chỉnh bao gồm các app khác nhau - từ các web app, Cơ Sở Dữ Liệu, cho đến Hệ Thống cache. etc. 

 

2. Tạo project Spring Boot và Cơ Sở Dữ Liệu:

Trọng tâm bài viết tập trubf hướng đến việc deploy app Spring Boot cũng như là MySQL nên người viết sẽ không trình bày chi tiết về code cũng như cách mà các thư viện hoạt động ra sao. Mà tập trung vào việc xây dựng một project Spring Boot đơn giản có chức năng kết nối Cơ Sở Dữ Liệu MySQL, và một Cơ Sở Dữ Liệu MySQL cũng đơn giản vừa đủ để demo tính năng.

Project Demo sau đây sẽ tạo ra một web app đơn giản có tính năng đọc, lưu và xóa các ghi chú (notes) cùng vài trường thông tin  đi kèm, như họ tên, địa điểm, ngày tháng.

2.1 Spring Boot:

Khởi tạo Project bằng website Spring Initializr, truy cập địa chỉ: https://start.spring.io 

Spring Initializr giúp chúng ta tạo nhanh một project Spring Boot cùng một số thư viện đi kèm được hỗ trợ sẵn một cách tiện lợi.

Tiếp theo chúng ta sẽ add thư viện Spring JPA, ở đây chúng ta sử dụng thư viện một cách tối thiểu nhất để đơn giản hóa project. 

Click chọn vào nút Add Dependecies ở khung bên phải

 

Và chọn thư viện JPA, chúng ta tìm nhanh thư viện bằng cách gõ tên của chúng:

Hoàn tất chọn thư viện, chúng ta bấm nút Generate bên dưới màn hình để download project về máy dưới dạng file zip.

 

Tổng quan Project:

Trong giới hạn bài viết sẽ không đề cập chi tiết về các annotation và cách sử dụng mà chỉ hướng dẫn nhanh để tạo ra các class Entity và Repository một cách đơn giản để phục vụ cho project demo.

Sau khi download và giải nén project được download về máy, chúng ta tiến hành import và tạo thêm các class như sau:

 

File 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 http://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.3.4.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring.version>2.3.4.RELEASE</spring.version>
    </properties>

    <groupId>org.example</groupId>
    <artifactId>deploy-springboot-mysql-docker</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>compile</scope>
        </dependency>

        <!-- spring-boot-starter-thymeleaf -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--spring data-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- Copy all project dependencies jar File to build lib Under the directory -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                    <mainClass>autocode.demo.docker.Main</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 

application.properties:

server.port=8080

# hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.datasource.initialization-mode=always
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect

Một điều các bạn cần lưu ý: chúng ta không khai báo các  param để kết nối tới MySQL trong file application.properties mà sẽ đọc từ file docker-compose.yaml - sẽ được giải thích chi tiết trong phần thực thi file docker-compose.yaml

 

Class Entity và Repository:

package autocode.demo.docker.models;

import lombok.Data;
import org.hibernate.annotations.Type;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
public class Note {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "created_ts")
    private Long createdTs;

    @Column(name = "updated_ts")
    private Long updatedTs;

    @Column(name = "note_content")
    @Type(type = "text")
    private String content;

    @Column(name = "note_title")
    @Type(type = "text")
    private String title;

}
package autocode.demo.docker.repositories;

import autocode.demo.docker.models.Note;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface NoteRepository extends JpaRepository<Note, Long> {

}

 

Class Controller và class Main:

package autocode.demo.docker.controllers;

import autocode.demo.docker.models.Note;
import autocode.demo.docker.repositories.NoteRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping(value = "/note")
public class NoteController {

    @Autowired
    private NoteRepository noteRepository;

    @GetMapping
    public ResponseEntity<List<Note>> getNotes() {
        return ResponseEntity.ok(noteRepository.findAll());
    }

    @GetMapping(value = "/{id}")
    public ResponseEntity<Note> getNoteById(@PathVariable(value = "id") long noteId) {
        return ResponseEntity.ok(noteRepository.getOne(noteId));
    }

    @PostMapping()
    public ResponseEntity<Long> persist(@RequestBody Note note) {
        noteRepository.save(note);
        return ResponseEntity.ok(note.getId());
    }
}
package autocode.demo.docker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "autocode.demo.docker")
public class Main {

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

 

Dockerfile:

Để có thể tạo được Container chứa Spring boot app, chúng ta cần khai báo một Dockerfile để tạo Docker image, như sau:

FROM adoptopenjdk/openjdk11:alpine-jre
COPY ./src/main/resources/templates/* /templates
COPY ./target/*.jar /app.jar
ENTRYPOINT ["java", "-jar" ,"/app.jar"]
EXPOSE 8080

Trước khi build Docker Image, chúng ta cần build file jar của Spring Boot app trước:

mvn clean install

Output:

[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ deploy-springboot-mysql-docker ---
[INFO] Installing /Users/nhatnguyen/working/deploy-springboot-mysql-docker/target/deploy-springboot-mysql-docker-1.0-SNAPSHOT.jar to /Users/nhatnguyen/.m2/repository/org/example/deploy-springboot-mysql-docker/1.0-SNAPSHOT/deploy-springboot-mysql-docker-1.0-SNAPSHOT.jar
[INFO] Installing /Users/nhatnguyen/working/deploy-springboot-mysql-docker/pom.xml to /Users/nhatnguyen/.m2/repository/org/example/deploy-springboot-mysql-docker/1.0-SNAPSHOT/deploy-springboot-mysql-docker-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  3.494 s
[INFO] Finished at: 2021-11-07T13:23:29+07:00
[INFO] ------------------------------------------------------------------------

 

Chúng ta tiến hành build Docker Image:

docker build -t spring-boot-app . --no-cache

Output:

[+] Building 2.7s (7/7) FINISHED                                                                                                                                                                                        
 => [internal] load build definition from Dockerfile                                                                                                                                                               0.0s
 => => transferring dockerfile: 36B                                                                                                                                                                                0.0s
 => [internal] load .dockerignore                                                                                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                                                                                    0.0s
 => [internal] load metadata for docker.io/adoptopenjdk/openjdk11:alpine-jre                                                                                                                                       1.3s
 => [internal] load build context                                                                                                                                                                                  0.8s
 => => transferring context: 41.49MB                                                                                                                                                                               0.8s
 => CACHED [1/2] FROM docker.io/adoptopenjdk/openjdk11:alpine-jre@sha256:164273edf6fa2a0599e98da2dc816e5d1725cec6f447e6f49db5fa87070edbcc                                                                          0.0s
 => [2/2] COPY ./../target/*.jar /app.jar                                                                                                                                                                          0.2s
 => exporting to image                                                                                                                                                                                             0.2s
 => => exporting layers                                                                                                                                                                                            0.2s
 => => writing image sha256:d315ecd088a304820c41fd7602731fb441a77cb1a33078a154af59401e6a648d                                                                                                                       0.0s
 => => naming to docker.io/library/spring-boot-app         

 

 

2.2 MySQL:

Trong bài viết này, chúng ta sẽ sử dụng 2 phương thức khác nhau để khai báo Image để tạo Container cho mỗi Service. Cụ thể, chúng ta sử dụng khai báo chính xác Docker Image cho Spring Boot app như mục trên đã trình bày, và sử dụng

phương thức build image mỗi khi thực thi docker-compose. Sự khác nhau giữa 2 phương thức này sẽ được đề cập chi tiết trong phần giải thích thực thi file docker-compose.

Tham khảo thêm tại Docker MySQL

Trước khi đi vào chi tiết của file docker-compose.yaml, chúng ta sẽ đi vào định nghĩa và cách sử dụng lệnh docker-compose.

 

3. Lệnh docker-compose:

Lệnh docker-compose là một command được hỗ trợ trong bộ Docker client api tương tự như lệnh docker.

Để sử dụng lệnh docker-compose , chúng ta cần phải xây dựng file docker-compose. Khái niệm compose khá quen với những bạn đã có kinh nghiệm làm việc với file compose của nodejs, ý nghĩa của file docker-compose cũng tương tự như thế.

3.1 FIle docker-compose.yaml

Các lệnh đi kèm với docker-compose:

  • version: phiên bản docker-compose đang sử dụng.
  • up: thực thi file docker-compose, khởi tạo các service được định nghĩa.
  • down: dừng docker-compose, hủy các service được định nghĩa trong file.

File docker-compose có phần mở rộng (extension - đuôi) là .yaml (hoặc yml) , dùng để định nghĩa những service (ứng dụng) cần được deploy với các thông số cần định nghĩa:  Tên service, tên Image, version:

Các bạn cần lưu ý chúng ta sẽ đặt tên file chính xác là docker-compose.yaml (hoặc docker-compose.yml) , aefefwef lệnh docker-compose khi được thực thi - sẽ tự động tìm file docker-compose.yaml trong folder hiện tại hoặc chúng ta sẽ phải chỉ định chính xác đường dẫn chính xác của file nếu chúng ta không đặt tên file compose là  docker-compose.yaml.

Ví dụ file docker-compose.yaml:

Hoàn tất quá trình thực thi file docker-compose, Docker engine sẽ tạo ra cho chúng ta những container từ các Image được khai báo cũng như mối quan hệ giữa chúng. Cụ thể ở bài viết này chúng ta sẽ khai báo cho ứng dụng Spring Boot sử dụng Cơ Sở Dữ LIệu MySQL cũng được tạo ra bởi file docker-compose này.

 

4. Định nghĩa file docker-compose.yaml và thực thi:

4.1 Clean up các Docker Volume:

Trước khi tiến hành thực thi bất kỳ docker-compose.yaml file nào, một tip nhỏ các bạn cần lưu ý là cần phải clean up tất cả các Docker Volume không còn sử dụng bằng command:

docker volume prune -f

Dọn dẹp các outdated Docker Volume không chỉ giúp chúng ta giải phóng bớt dung lượng ổ đĩa đang cứng đang bị chiếm dụng không cần thiết mà còn bởi một điều quan trọng như sau: Docker mặc định sẽ bỏ qua tất cả thao tác copy file từ ổ cứng của localhost, ví dụ như chúng ta cần copy 1 file SQL script để chạy khởi tạo một số config cần thiết (schema chẳng hạn), nếu chúng ta không cleanup đi ổ cứng thì file SQL này sẽ không được thực thi.

 

4.2 Tạo file docker-compose.yaml:

Chúng ta tạo file docker-compose.yaml trong cùng thư mục với Project và định nghĩa như sau:

version: "3.8"

services:
  mysqldb:
    image: mysql:5.7
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=notes2
    ports:
      - "3307:3306" #<local-port>:<container-port>
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/1.sql
      - db:/var/lib/mysql
  app:
    depends_on:
      - mysqldb
    image: spring-boot-app
    restart: always
    ports:
      - "8181:8080" #<local-port>:<container-port>
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysqldb:3306/notes2?autoReconnect=true&failOverReadOnly=false&maxReconnects=10
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: root
    stdin_open: true
    tty: true

volumes:
  db:

Chúng ta sẽ giải thích một số điểm cần lưu ý như sau:

- Cần tuân thủ đúng thứ tự thụt dòng của các thuộc tính trên từng dòng. File docker-compose sử dụng định dạng file yaml, do đó các bạn cần lưu ý đến các thuộc tính con bên dưới phải được thụt vào tương ứng với thuộc tính cha của nó.

- version: 3.8: Docker engine dựa vào số phiên bản để quyết định cách thực thi file, tuy nhiên chúng ta được khuyến khich sử dụng mới nhất. Tham khảo thêm tại trang tài liệu chính thức của Docker.

- services: khai báo các services - các app để deploy - ở đây chúng ta sẽ có Spring Boot (tên app) và MySQL (tên mysqldb). Tên của các service ngoài ý nghĩa dùng để phân biệt khi đọc file mà còn dùng để các service communicate với nhau. Ví dụ, trong phần khai báo của Spring Boot chúng ta phải dùng tên service của MySQL (mysqldb) để trỏ tới container chứa MySQL.

- image: khai báo Docker Image để tạo Container cho service. Sử dụng khai báo này khi Image của service thường xuyên thay đổi, ví dụ như Spring Boot app được update mới mỗi ngày. Thông số cần khai báo là tên Image cùng với version.

- build: cũng cùng có chức năng tạo Docker image như image ở trên, tuy nhiên Docker Image chỉ được tạo khi chúng ta thực thi command "docker-compose up". Sử dụng khai báo này k hi không hoặc rất it thay đổi Image của Service mà chỉ thay đổi data bên trong, ví dụ như các cơ sở dữ liệu hoặc Cache, etc. Thông số cần khai báo là đường dẫn của thư mục chứa file Dockerfile dùng để build Image.

- depends_on: khai báo khi một Service cần phải chờ một Service khác được tạo xong rồi mới được tạo, trong ví dụ của chúng ta, Spring Boot app cần phải chờ MySQL được khởi tạo xong.

- environment: là một trong những khai báo quan trọng nhất và được sử dụng khá nhiều trong các dự án thực tế. Vì lý do bảo mật mà các thông số liên quan tới username, password sẽ không được khai báo trực tiếp trong source code mà được khai báo dưới dạng các environment variable. Và chỉ được đọc và load vào bên trong Container khi thực thi tạo các service từ file docker-compose.yaml.

 

4.4 Thực thi file docker-compose.yaml:

Thực thi command

docker-compose up

Output như sau:

Creating network "deploy-springboot-mysql-docker_default" with the default driver
Creating deploy-springboot-mysql-docker_mysqldb_1 ... done
Creating deploy-springboot-mysql-docker_app_1     ... done
Attaching to deploy-springboot-mysql-docker_mysqldb_1, deploy-springboot-mysql-docker_app_1

 

Log MySQL:

Attaching to deploy-springboot-mysql-docker_mysqldb_1, deploy-springboot-mysql-docker_app_1
mysqldb_1  | 2021-11-07 12:03:11+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.36-1debian10 started.
mysqldb_1  | 2021-11-07 12:03:11+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
mysqldb_1  | 2021-11-07 12:03:11+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.36-1debian10 started.
mysqldb_1  | 2021-11-07T12:03:11.571242Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
mysqldb_1  | 2021-11-07T12:03:11.572326Z 0 [Note] mysqld (mysqld 5.7.36) starting as process 1 ...
mysqldb_1  | 2021-11-07T12:03:11.575071Z 0 [Note] InnoDB: PUNCH HOLE support available
mysqldb_1  | 2021-11-07T12:03:11.575117Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
mysqldb_1  | 2021-11-07T12:03:11.575123Z 0 [Note] InnoDB: Uses event mutexes
mysqldb_1  | 2021-11-07T12:03:11.575131Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used for memory barrier
mysqldb_1  | 2021-11-07T12:03:11.575138Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.11
mysqldb_1  | 2021-11-07T12:03:11.575143Z 0 [Note] InnoDB: Using Linux native AIO

.....

 

Log Spring Boot:

app_1      | 
app_1      |   .   ____          _            __ _ _
app_1      |  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
app_1      | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
app_1      |  \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
app_1      |   '  |____| .__|_| |_|_| |_\__, | / / / /
app_1      |  =========|_|==============|___/=/_/_/_/
app_1      |  :: Spring Boot ::        (v2.3.4.RELEASE)
app_1      | 
app_1      | 2021-11-07 12:03:16.351  INFO 1 --- [           main] autocode.demo.docker.Main                : Starting Main v1.0-SNAPSHOT on f02a0ef04f07 with PID 1 (/app.jar started by root in /)
app_1      | 2021-11-07 12:03:16.355  INFO 1 --- [           main] autocode.demo.docker.Main                : No active profile set, falling back to default profiles: default
app_1      | 2021-11-07 12:03:17.952  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
app_1      | 2021-11-07 12:03:18.131  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 156ms. Found 1 JPA repository interfaces.
app_1      | 2021-11-07 12:03:19.289  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
app_1      | 2021-11-07 12:03:19.316  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
app_1      | 2021-11-07 12:03:19.317  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.38]
app_1      | 2021-11-07 12:03:19.454  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
app_1      | 2021-11-07 12:03:19.454  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2987 ms
app_1      | 2021-11-07 12:03:19.891  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
app_1      | 2021-11-07 12:03:20.030  INFO 1 --- [         task-1] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
app_1      | 2021-11-07 12:03:20.088  WARN 1 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
app_1      | 2021-11-07 12:03:20.266  INFO 1 --- [         task-1] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.21.Final
app_1      | 2021-11-07 12:03:20.670  WARN 1 --- [           main] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
app_1      | 2021-11-07 12:03:20.698  INFO 1 --- [         task-1] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
app_1      | 2021-11-07 12:03:20.843  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
app_1      | 2021-11-07 12:03:20.846  INFO 1 --- [           main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories…
app_1      | 2021-11-07 12:03:20.984  INFO 1 --- [         task-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
app_1      | 2021-11-07 12:03:21.588  INFO 1 --- [         task-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
app_1      | 2021-11-07 12:03:21.648  INFO 1 --- [         task-1] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
app_1      | 2021-11-07 12:03:23.218  INFO 1 --- [         task-1] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
app_1      | 2021-11-07 12:03:23.239  INFO 1 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
app_1      | 2021-11-07 12:03:23.670  INFO 1 --- [           main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
app_1      | 2021-11-07 12:03:23.683  INFO 1 --- [           main] autocode.demo.docker.Main                : Started Main in 8.17 seconds (JVM running for 8.93)

 

Kiểm tra các container vừa được tạo:

docker ps -a
CONTAINER ID   IMAGE                                    COMMAND                  CREATED          STATUS                       PORTS     NAMES
f02a0ef04f07   spring-boot-app                          "java -jar /app.jar"     10 minutes ago   Exited (137) 2 minutes ago             deploy-springboot-mysql-docker_app_1
31492ee97435   deploy-springboot-mysql-docker_mysqldb   "docker-entrypoint.s…"   10 minutes ago   Exited (137) 2 minutes ago             deploy-springboot-mysql-docker_mysqldb_1

 

4.3 Test các API CRUD trong app Spring Boot:

Get all các note trong DB:

 curl localhost:8181/note

Do trong phần khai báo của Spring Boot app, chúng ta đã map port 8181 của localhost với port 8080 của container. Nghĩa là chúng ta truy xuất app của Spring Boot qua port 8181 của máy localhost.

Output:

[]

Kết quả nhận về là một empty array vì chúng ta chưa có Note nào trong Database.

 

Thêm note vào DB:

 curl -X POST  localhost:8181/note -H "Content-Type: application/json"  -d '{"content": "This is my note which is created for testing", "title": "Note 1"}'

Chúng ta gửi một request tới API thêm note của Spring Boot app.

Output:

1

Kết quả nhận về là Id của Note vừa mới được tạo, nghĩa là chúng ta vừa thêm Note đầu tiên với id = 1.

 

Kiểm tra lần nữa Get all các note từ Spring Boot:

 curl localhost:8181/note

Output:

[{"id":1,"createdTs":null,"updatedTs":null,"content":"This is my note which is created for testing","title":"Note 1"}]

Kết quả nhận về là một array gồm 1 phần tử, là Note vừa được tạo.

 

Kiểm tra dữ liệu trực tiếp trong DB:

Lấy container Id của MySQL:

 docker ps -a | grep mysql
2db9a4332faf   deploy-springboot-mysql-docker_mysqldb   "docker-entrypoint.s…"   12 minutes ago   Up 12 minutes   33060/tcp, 0.0.0.0:3307->3306/tcp, :::3307->3306/tcp   deploy-springboot-mysql-docker_mysqldb_1

 

Truy cập vào Database trong Container:

docker exec -it 2db9a4332faf  mysql -u root -proot

Query table note:

select * from notes2.note;
+----+----------------------------------------------+------------+------------+------------+
| id | note_content                                 | created_ts | note_title | updated_ts |
+----+----------------------------------------------+------------+------------+------------+
|  1 | This is my note which is created for testing |       NULL | Note 1     |       NULL |
+----+----------------------------------------------+------------+------------+------------+
1 row in set (0.01 sec)

 

Tới đây đã có thể kết luận chúng ta đã hoàn thành việc deploy 2 services Spring Boot và CSDL MySQL bằng command docker-compose. Và có thể sử dụng được các API của Spring Boot, đồng thời Spring Boot có thể kết nối và đọc/ghi dữ liệu được xuống CSDL MySQL. 

Chúc các bạn thành công.

AutoCode.VN

minhnhatict@gmail.com Docker