https://victorydntmd.tistory.com/325
https://velog.io/@max9106/Spring-Boot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1
위 두 글을 바탕으로 구성하고, 진행했다.
1. 수정사항, 의존성 설정, 전체적 구조 잡기
개발환경
IDEA : VS Code
Java : openjdk 11
Gradle : 7.0.2
Spring Boot : 2.5.2
Docker : 20.10.7
라이브러리
MySQL : DB
JPA : DB와 연동을 위해 필요함
Thymeleaf : resource/templates에 있는 html 파일을 읽어오기 위해 필요함
Lombok : NoArgsConstructor라는 Constructor를 읽어오기 위해 필요함
프로젝트 생성 및 수정사항
지난 게시글, Docker로 백엔드(Spring + Mysql) 구성하기에서 spring project도 생성했고 docker 위에 mysql도 올렸고 연동하는 것도 끝냈다. 다만 한 가지 수정할 것이 있다.
*** MySQL + Spring Boot on Docker로 구성했는데 Spring이 MySQL에 접속을 못하는 문제 1
내가 겪은 문제는 다음와 같았다. Spring에 MySQL을 연동하려면 application.properties와 build.gradle에 mysql 관련 정보를 넣었어야 했다. 그 중 중요했던 게, application.properties에 mysql의 url이 무엇인지 입력하는 것이다.
// application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/board?autoReconnect=true&useSSL=false&useUnicode=yes&characterEncoding=UTF-8&autoReconnectForPools=true&serverTimezone=UTC
이렇게 입력했었다. 지금은 서버에 올리는 게 아니라 local에서 돌리는 거기 때문에. 여기서 문제점은...
1) 먼저 spring의 build를 위해서는 docker 내에 있는 mysql container가 running인 상태여야 한다.
./gradlew build를 할 때 build.gradle에 내가 포함한 것들에 대한 test를 진행하는데 mysql container가 stop 상태인 경우 hikaripool test가 fail이 뜨면서 build가 실패한다. 보통 개발환경은 local에 구축하는데 나는 docker에 깔아서 그렇다.
2) 즉, local 환경에서 build를 위해서는 application.properties의 mysql 주소가 mysql://localhost:3306/... 으로 되어 있어야 한다. 그런데 docker 위에 올린 spring project의 build를 위해서는, 내가 docker-compose.yml 파일을 작성할 때 적었던 mysql container name을 적어야 한다. 즉, application.properties의 mysql 주소가 mysql://mysqlcomp:3306/...로 된 상태로 build해야 한다는 것이다.
어? 그런데 이러면 local에서 build가 안 된다.
3) local build를 위해선 localhost로, docker 위에서 build를 위해서는 mysqlcomp로?
그런데 docker에 올리는 건 .jar 파일, 즉 build된 파일이다. local에서 빌드 된 파일을 올리는건데, local build를 하려면 localhost로 작성되어 있어야 한다. 그러면 docker 위에서 spring이 mysql과 연동을 못한다....
-> 위 글을 보고 해결했다. docker-compose.yml의 spring container에 environment라는 환경변수를 지정해서, spring이 접속할 DB url을 따로 지정함으로써 해결했다. 이렇게 하는 이유도 잘 적혀 있었다. 역시 설명을 잘 읽어야 한다...
조금만 기술하자면, 일반적으로 local에서는 localhost:3306/~으로 접속하는데 docker의 경우 각 container는 각각의 ip를 할당받게 된다, 그래서 mysql container ip주소와 달라서 접속이 안된다. 따라서 접속할 DB url을 따로 지정해 준다고 한다.
docker-compose.yml
version: "3"
services:
spring:
build:
context: ./backend/testcompose
dockerfile: Dockerfile
volumes:
- ./backend/testcompose/build/libs:/spring
ports:
- 8080:8080
restart: always
container_name: springcomp
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysqlcomp:3306/board?autoReconnect=true&useSSL=false&useUnicode=yes&characterEncoding=UTF-8&autoReconnectForPools=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: aa
networks:
- backend-network
mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: aa
volumes:
- mytestdb:/var/lib/mysql
ports:
- 3306:3306
container_name: mysqlcomp
networks:
- backend-network
volumes:
mytestdb:
networks:
backend-network:
spring service의 environment를 지정한 것을 볼 수 있다.
*** MySQL + Spring Boot on Docker로 구성했는데 Spring이 MySQL에 접속을 못하는 문제 2
앞 문제랑 연관이 있는 건지 모르겠는데, 처음에 외부 접속이 허용이 안된다고만 나와서 권한 문제인가 보다 싶어서 아래 글을 참고했다. mysql에서 원격 접속을 허가해주는 구문을 날려주는 것이다.
https://darrengwon.tistory.com/m/1383?category=858368
간단하게 서술하자면 아래 단계를 통해 'root' 계정의 '%', 모든 IP 접속을 허가한다는 내용이다.
docker-compose up -d // 컨테이너 생성
docker exec -it mysqlcomp bash // 컨테이너에 접속
mysql -u root -p // mysql에 접속
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password by '{password}';
FLUSH PRIVILEGES;
*** MySQL + Spring Boot on Docker로 구성했는데 Spring이 MySQL에 접속을 못하는 문제 3
Mysql에 docker-compose.yml에서 spring 내의 SPRING_DATASOURCE_URL에서 board라는
에서 설정했는데 board라는 database가 없어서 그렇다.
docker exec -it mysqlcomp mysql -u root -p
입력해서 mysql에 들어가 board라는 database를 만들자.
*** 요약하자면...
1) application.properties에는 localhost로 기재
2) docker-compose.yml의 spring service environment의 SPRING_DATASOURCE_URL은 mysqlcomp로 작성
3) mysql에 접속해 board database 만들기
*** 프로젝트 처음부터 따라가고 있는데 .jar파일이 없어서 docker build가 안됨!
그냥 build/lib에 아무 JAR 파일 넣고 docker-compose 돌리고, mysql만 켠 상태로 spring build하면 될 것이다.
- 의존성 설정
spring에서 의존성은 크게 2가지에 넣는다. build.gradle에 넣고, src/main/resources/application.properties 안에 설정을 넣는다. 지금은 MySQL, JPA, Thymeleaf, Lombok 4가지를 할 거다.
먼저 build.gradle의 dependencies 안에 위 세 줄이 먼저 있었다. lombok, jpa, mysql, thymeleaf 순서대로 개행으로 구분했다.
build.gradle
plugins {
id 'org.springframework.boot' version '2.5.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}
test {
useJUnitPlatform()
}
다음으로 application.properties에 설정을 넣는다. 각 옵션에 대해 간단하게만 해석하겠다.
mysql 같은 경우에 3, 4, 5번줄은 docker 위에서 돌릴려면 필요없지만 local에서 build 테스트 하고 작동확인 하고 올리는 게 더 편할 것 같아서 이렇게 작성한다. 어차피 docker-compose할 때는 docker-compose.yml에 있는 환경변수가 더 상위의 것이라 그게 먼저 실행되는 것 같다.
application.properties
#MySQL 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/board?autoReconnect=true&useSSL=false&useUnicode=yes&characterEncoding=UTF-8&autoReconnectForPools=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=aa
#JPA
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=true
spring.datasource.driver : driver 어떤거 쓸건지에 대한 정보. 나는 jdbc의 mysql driver를 사용함
spring.datasource.url : datasource url이 어딘지. mysql의 접속 url이 어디인지 쓰면 된다. 나는 localhost에 포트가 3306이니 localhost:3306, 그 뒤의 board는 board라는 DB schema에 접속하겠다는 거다. 그 뒤에 있는 설정들은 읽어보면 알 수 있는 정보들.
그 아래 2줄은 보면 알 수 있다.
spring.jpa.show-sql : sql문을 날릴 때 log에 출력하는지
spring.jpa.database : jpa에서 이용할 db가 어떤 건지
spring.jpa.generate-ddl : spring.jpa.hibernate.ddl-auto 옵션을 사용할 것인지. 사용할 거면 true.
spring.jpa.hibernate.ddl-auto - 아래 게시글 참조. 참고한 포스트에서는 해당 옵션을 create로 했던데 이러면 spring을 껐다 켤 때마다 DB에 있는 모든 내용이 날아가 있다! create는 기존에 있는 schema를 삭제하고 새로 만들게 되는 옵션을 택했기 때문이다. 그래서 나는 update를 선택했다.
https://engkimbs.tistory.com/794
- 전체적 구조 잡기
전체 directory 구성은 위와 같다. 왼쪽은 docker file을 보여주기 위한 거, 프로젝트 명은 testcompose고 했고 src 안의 것은 오른쪽과 같다. 먼저 front가 구현되어 있다는 가정 하게 내가 참고한 블로그들로 가서 resources 폴더 안에 프론트 파일을 전부 넣자.
그리고 spring 같은 경우에는 naming이 굉장히 중요하므로, naming notation을 조금 신경 쓰자. 파일 이름이나 class 명은 복합어인데, 복합어의 첫 글자는 대문자로. 변수 같은 경우에는 첫 글자는 소문자로, 복합어일 때는 camel casing을 적용. 예를 들어 위 .java 파일은 전부 BoardService와 같이 복합어의 첫 글자는 대문자로 적용했다. 그리고 아래와 같이 class 명의 복합어 첫 글자는 대문자, 변수명은 첫 글자만 소문자로.
BoardService boardService;
그럼 이제 각 component가 뭘 하는지 보자.
- src/main/java/com/example/testcompose/controller
url과 실행 함수를 매핑하고, service를 호출함.
- src/main/java/com/example/testcompose/service
business logic을 구현. repository에서 가져온 data를 controller에게 넘겨주거나 할 일을 함.
- src/main/java/com/example/testcompose/domain/entity
DB의 table과 매칭되는 class.
- src/main/java/com/example/testcompose/domain/repository
JPA에서 interface에 맞는 규칙대로 입력하면 알아서 데이터 조회/조작을 해 줌.
- src/main/java/com/example/testcompose/dto
view, controller, service, repository 사이에서 주고받을 객체, Data Transfer Object를 정의함.
- src/main/resources/static
css 정적 자원을 모아 둠
- src/main/resources/templates
HTML template들을 모아 둠.
- 잘 실행되나 확인
간단한 controller 하나를 넣어 보자. 다른 거 다 냅두고 이렇게만 작성해 두자.
src/main/java/com/example/testcompose/controller/BoardController.java
package com.example.testcompose.controller;
import com.example.testcompose.dto.BoardDto;
import com.example.testcompose.service.BoardService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import lombok.AllArgsConstructor;
@Controller
@AllArgsConstructor
public class BoardController{
private BoardService boardService;
@GetMapping("/")
public String list(){
return "board/list.html";
}
@GetMapping("/post")
public String post(){
return "board/write.html";
}
}
- @Controller
이 class가 controller라는 것을 명시해 주고 리턴할 것을 적어준다. template 경로를 작성해 주거나 redirect 해 주면 된다고 한다. 추후에 BE만 구현하게 된다면 @RestController를 이용해 API 응답만 할 수 있게 해 준다.
- @GetMapping
url에 대한 요청을 처리해 준다. return은 resources/template에 있는 html을 리턴해 줄 거라는 뜻이다.
자, docker-compose up을 입력하고 mysql container만 설치한 후에 VS Code에서 F5를 눌러보자.