* 해당 글에선 docker-compose 방식이 아닌 직접 터미널에 명령어를 작성하면서 진행하였습니다.
1️⃣ Spring boot 프로젝트
1) 프로젝트 생성 및 설정
https://start.spring.io/ 에서 프로젝트를 하나 생성한다.
Project
- Project : Gradle - Groovy
- Language : Java
- Spring Boot Version : SNAPSHOT 아닌 최신 버전 (여기선 3.2.3)
- Packaging : Jar
- Java Version : 17
Dependencies
- Spring Web
- Thymeleaf
- Spring Data JPA
- Lombok
- MySQL Driver
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.3'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'basic'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
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'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mydb?createDatabaseIfNotExist=true
username: root
password: 1234
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
show_sql: true
2) 애플리케이션 구현
간단한게 회원 등록하는 애플리케이션을 만들어보자.
Member.class
@Getter
@NoArgsConstructor
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
}
RequestMember.class
public record RequestMember(String name, Integer age) {
}
MemberRepository.class
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
MemberController.class
@RequiredArgsConstructor
@Controller
public class MemberController {
private final MemberRepository memberRepository;
@GetMapping("/")
public String home(Model model) {
model.addAttribute("member", new Member());
return "home";
}
@PostMapping("/")
public String addMember(@ModelAttribute RequestMember request) {
Member member = new Member(request.name(), request.age());
memberRepository.save(member);
return "redirect:/";
}
@GetMapping("/members")
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "membersForm";
}
}
3) 뷰 구현
home.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/style.css">
<title>Home</title>
</head>
<body>
<div class="container">
<h2>Home</h2>
</div>
<div class="container">
<div class="member-input">
<form th:action th:method="post" th:object="${member}">
<div class="input-group">
<label for="name">이름</label>
<input type="text" id="name" name="name" placeholder="이름을 입력하세요" required/>
</div>
<div class="input-group">
<label for="age">나이</label>
<input type="text" id="age" name="age" placeholder="나이를 입력하세요" required/>
</div>
<div class="container">
<button type="submit">제출</button>
</div>
<a th:href="@{/members}">목록</a>
</form>
</div>
</div>
</body>
</html>
membersForm.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/style.css">
<title>Members</title>
</head>
<body>
<div class="container">
<h2>Members</h2>
</div>
<div class="container">
<div class="member-list">
<table th:border="1">
<thead>
<tr>
<th>ID</th>
<th>이름</th>
<th>나이</th>
</tr>
</thead>
<tbody>
<tr th:each="member: ${members}">
<td th:text="${member.getId()}"></td>
<td th:text="${member.getName()}"></td>
<td th:text="${member.getAge()}"></td>
</tr>
</tbody>
</table>
<a style="margin-top: 5px" th:href="@{/}">메인</a>
</div>
</div>
</body>
</html>
style.css
body {
background: beige;
}
.container {
display: flex;
justify-content: center;
}
.member-input, .member-list {
display: flex;
flex-direction: column;
}
.input-group {
margin-bottom: 10px;
}
input[type="text"],
input[type="number"] {
padding: 5px;
width: 200px;
}
2️⃣ Docker
: 일반화한 코드
: 사용한 코드
1) network 생성
우선 터미널에 접속해 'docker network create [네트워크 이름]' 명령어를 입력한다. 여기서는 'member-network'로 설정한다.
그리고 'docker network ls' 명령어로 네트워크가 잘 생성되었는지 확인하자.
docker network create <네트워크 이름>
docker network create member-network
docker network ls
이제 해당 네트워크에 MySQL 컨테이너와 Spring boot 컨테이너를 생성하여 연결해주면 된다.
2) MySQL 이미지 및 컨테이너 생성
터미널에 접속해 MySQL 이미지를 다운로드하여 컨테이너를 생성한다. 이후 컨테이너 목록을 확인하여 잘 생성되었는지 보자.
docker run -d --network <네트워크 이름> --name <DB 이름> -p 3306:3306 -e MYSQL_ROOT_PASSWORD=<비밀번호> mysql
docker run -d --network member-network --name mysql-db -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234 mysql
- -d : daemon 으로 실행 (백그라운드 실행)
- --network member-network : 이전에 만든 member-network 에 연결
- --name mysql-db : 컨테이너 이름 지정
- -p 3306:3306 : 3306 포트로 들어오는 요청을 3306 포트로 연결
- -e MYSQL_ROOT_PASSWORD=1234 : 루트 비밀번호 지정
- mysql : mysql 이미로부터 컨테이너 생성
이전에 만든 'member-network' 에 잘 연결되어있는지 확인한다.
docker network inspect <네트워크 이름>
docker network inspect member-network
위처럼 "Containers" 에 mysql-db가 있다면 잘 연결된 것이다.
그런데 이 상태에서 애플리케이션을 실행하면 오류가 발생한다. 당연하게도 이미지를 통해 컨테이너를 만들기만 했지 실제 데이터베이스는 생성하지 않았기 때문이다.
3) MySQL 데이터베이스 생성
'application.yaml' 파일에서 데이터베이스 이름을 'mydb'로 설정했으니 해당 이름으로 데이터베이스를 생성하자.
방금 만든 mysql-db 컨테이너에 접속해 데이터베이스를 생성한다. 비밀번호는 루트 비밀번호로 지정한 1234를 입력한다.
그리고 'exit'를 입력해 mysql을 빠져나온다.
docker exec -it <DB 이름> mysql -u root -p
docker exec -it mysql-db mysql -u root -p
create database mydb;
4) 애플리케이션 테스트
이제 데이터베이스를 설정했으니 애플리케이션을 실행해보자. 다음과 같은 화면이 나온다면 정상적으로 설정된 것이다.
테스트를 성공했다면 이제 'application.yaml'에서 데이터베이스 이름을 바꿔줘야 한다. 현재는 localhost이지만 Spring boot를 도커로 띄운다면 mysql-db 컨테이너와 연결할 것이기 때문이다.
application.yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql-db:3306/mydb?createDatabaseIfNotExist=true
username: root
password: 1234
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
show_sql: true
5) Dockerfile 생성
Dockerfile을 생성해야 한다. 우선 프로젝트의 루트 경로에 파일을 만든다.
Dockerfile
FROM openjdk:17-alpine
ARG JAR_FILE=*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
6) jar 파일 생성
인텔리제이 터미널을 열고 해당 프로젝트의 루트 경로로 들어간 다음 아래 명령어를 입력해 jar 파일을 생성한다.
./gradlew build
/build/libs 폴더에 들어가면 jar 파일이 두 개 생길 것이다. 해당 폴더에 아까 만든 Dockerfile을 넣어준다.
7) Spring boot 이미지 및 컨테이너 생성
이미지 생성
우선 jar 파일을 통해 이미지를 만들어야 한다. 다음 명령어를 입력하자. 마지막 . 까지 꼭 입력해야 한다.
그리고 m1 노트북을 사용한다면 '--platform linux/x86_64'를 넣어줘야 한다.
docker build -t <이미지 이름> --platform linux/x86_64 .
docker build -t spring-member --platform linux/x86_64 .
한 가지 주의할 점은 위 명령어를 해당 프로젝트의 /build/libs 폴더까지 들어간 후에 실행해야 한다.
만약 그렇지 않다면 jar 파일이 있는 경로를 찾아 이미지 이름 뒤에 붙여주면 된다.
docker build -t <이미지 이름> <.jar 경로> --platform linux/x86_64 .
빌드가 잘 되었다면 위처럼 나온다. 이제 컨테이너만 생성해주면 된다.
컨테이너 생성
docker run -d --name <컨테이너 이름> --network <네트워크 이름> -p 8080:8080 <이미지 이름>
docker run -d --name spring-app --network member-network -p 8080:8080 spring-member
마지막으로 네트워크에 잘 연결되었는지 확인한다.
"Containers" 에 두 개의 컨테이너가 잘 연결되었다. Doker Desktop 또는 docker ps 명령어를 확인하면 두 개의 컨테이너가 잘 띄워졌다.
8) 애플리케이션 실행
이제 localhost:8080 으로 접속하면 인텔리제이로 실행하지 않아도 잘 연결되는 것을 볼 수 있다.
이름과 나이를 입력하고 제출한 뒤, 목록으로 접속해보자. 실제로 데이터도 잘 저장되었다.