5 분 소요

IDE의 데이터베이스 연동 기능을 이용하여 제대로 구동 중인지 다시 한 번 확인 가능하다.

IntelliJ에서 확인하면 아래와 같은 모습이다.

Connected DB

User와 Password에는 MongoDB 컨테이너를 docker exec 하여 createUser() 했던 내용으로 인증 과정을 거치면 된다.

이를 application.properties 혹은 application.yml로 적용하면 된다. (필자는 use guest_book로 새로운 데이터 베이스를 구축했다.)

properties라면

spring.data.mongodb.host=localhost  
spring.data.mongodb.port=27017  
spring.data.mongodb.database=guest_book

yml이라면

spring: 
	data: 
		mongodb: 
			host: localhost
			port: 27017
			database: guest_book

와 같다.

애플리케이션과 DB 연동 정보를 입력했으니 연동해보자.

일단 위에서 비활성화시켜 둔 MongoDB의 의존성을 다시 활성화시켜 주자.

dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter'  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
      
    implementation 'org.mongodb:mongodb-driver-sync:4.10.2'  
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'  
  
    compileOnly 'org.projectlombok:lombok'  
    annotationProcessor 'org.projectlombok:lombok'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
  
}

이제 소스를 작성해보자.

크게

  • 데이터 형식을 잡는 Model
  • DB 동작을 수행하는 Repository
  • 동작을 실행하는 Service
  • 사용자의 동작을 받는 Controller

를 작성할 것이며, 프로젝트 구조는

structure

와 같다.

Model

데이터 핸들링을 위한 Model 객체를 생성한다.

package com.jaehoonman.guestbookspringboot.model;  
  
import lombok.Data;  
import org.springframework.data.annotation.Id;  
import org.springframework.data.mongodb.core.mapping.Document;  
  
import java.util.Date;  
  
@Data //Getter, Setter lombok 자동 설정.  
@Document(collection = "guest_book") // Mapping collection 지정.  
public class GuestBook {  
  
    @Id  
    private String id;  
  
    private String title;  
    private String writer;  
    private String contents;  
  
    private Date createdTime = new Date();  
}

(날짜는 제대로 안 나오긴 하지만 일단 테스트니…)

Repository

DB와 상호 작용하며 동작을 수행하는 Repository는 간단하게 MongoRepository 인터페이스를 extends 하는 것으로 기본 동작들을 제공받을 수 있다.

package com.jaehoonman.guestbookspringboot.repository;  
  
import com.jaehoonman.guestbookspringboot.model.GuestBook;  
import org.springframework.data.mongodb.repository.MongoRepository;  
import org.springframework.stereotype.Repository;  
  
@Repository  
public interface GuestBookRepository extends MongoRepository<GuestBook, Long> {
	GuestBook findById(String id);  
	void deleteById(String id);
}

버전 차이인지는 잘 모르겠지만 findById와 같이 Id값을 통해 데이터를 찾는 기능이 동작하지 않아 커스텀 메서드를 만들었다. (MongoRepository<GuestBook, Long> 처럼 지정한 ID 타입이 실제 @Id 필드 타입(String)과 다른 것이 원인일 수 있다.)

Service

사용자의 요청을 Controller에서 전달 받아 수행하고 싶은 동작을 지정하는 Service 레이어를 작성하자.

먼저 수행할 동작의 인터페이스를 지정한다.

package com.jaehoonman.guestbookspringboot.service;  
  
import com.jaehoonman.guestbookspringboot.model.GuestBook;  
import org.springframework.stereotype.Service;  
  
import java.util.List;  
  
@Service  
public interface GuestBookService {  
  
    GuestBook createGuestBook(GuestBook guestBook);  
  
    List<GuestBook> getAllGuestBooks();  
    GuestBook getGuestBookById(String id);  
  
    void deleteGuestBookById(String id);  
  
}

이후 해당 서비스를 implements하여 Repository와 연동, 수행할 동작을 지정해주자.

package com.jaehoonman.guestbookspringboot.service.impl;  
  
import com.jaehoonman.guestbookspringboot.model.GuestBook;  
import com.jaehoonman.guestbookspringboot.repository.GuestBookRepository;  
import com.jaehoonman.guestbookspringboot.service.GuestBookService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
  
import java.util.List;  
  
@Service  
public class GuestBookImpl implements GuestBookService {  
  
    private final GuestBookRepository guestBookRepository;  
  
    @Autowired  
    public GuestBookImpl(GuestBookRepository guestBookRepository){  
        this.guestBookRepository = guestBookRepository;  
    }  
  
    @Override  
    public GuestBook createGuestBook(GuestBook guestBook) {  
        return this.guestBookRepository.save(guestBook);  
    }  
  
    @Override  
    public List<GuestBook> getAllGuestBooks() {  
        return this.guestBookRepository.findAll();  
    }  
  
    @Override  
    public GuestBook getGuestBookById(String id){  
        return this.guestBookRepository.findById(id);  
    }  
  
    @Override  
    public void deleteGuestBookById(String id) {  
        this.guestBookRepository.deleteById(id);  
    }  
  
}

Controller

HTTP Method에 맞는 처리를 수행하도록 Controller를 작성하자.

package com.jaehoonman.guestbookspringboot.controller;  
  
import com.jaehoonman.guestbookspringboot.model.GuestBook;  
import com.jaehoonman.guestbookspringboot.service.GuestBookService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.List;  
  
@RestController  
@RequestMapping("/guestbook")//호스트 맵핑
public class GuestBookController {  
  
    //서비스 주입  
    private final GuestBookService guestBookService;  
  
    @Autowired  
    public GuestBookController(GuestBookService guestBookService){  
        this.guestBookService = guestBookService;  
    }  
  
    //GET HTTP 메서드로 모든 데이터 읽어오기.  
    @GetMapping  
    public List<GuestBook> getAllGuestBook(){  
        return guestBookService.getAllGuestBooks();  
    }  
  
    //GET HTTP 메서드로 id에 해당하는 데이터 읽어오기.  
    @GetMapping("/{id}")  
    public GuestBook getGuestBookByTitle(@PathVariable String id){  
        return guestBookService.getGuestBookById(id);  
    }  
  
    //POST HTTP 메서드로 새로운 데이터 insert.  
    @PostMapping  
    public GuestBook createGuestBook(@RequestBody GuestBook guestBook){  
        return guestBookService.createGuestBook(guestBook);  
    }  
  
    //DELETE HTTP 메서드로 id에 해당하는 데이터 삭제하기.  
    @DeleteMapping("/{id}")
    public void deleteGuestBookById(@PathVariable String id){  
        guestBookService.deleteGuestBookById(id);  
    }  
  
  
}

이제 서비스를 실행시키고 동작을 확인하자. Postman과 같은 툴을 사용해도 되지만 필자는 터미널에서 curl 커맨드를 사용해보겠다.

동작 확인

데이터 삽입

curl -d '{"title":"안녕", "writer":"재훈맨", "contents":"하세요"}' -H "Content-Type: application/json" -X POST http://localhost:8080/[ENDPOINT]

(id는 자동 생성되기에 따로 지정하지 않는다.)

조회

전체 조회

curl -X GET http://localhost:8080/[ENDPOINT]

id로 조회

curl -X GET http://localhost:8080/[ENDPOINT]/[id]

삭제

curl -X DELETE http://localhost:8080/[ENDPOINT]/[id]

working-test

동작을 확인하였으니 이제 Docker Compose로 구성해보자.

Docker Compose 작성 및 컨테이너화

docker-compose.yml 파일은 Dockerfile과 같은 application Root 위치에 생성한다.

version: "3" # Docker compose의 버전 선택 
services: # 컨테이너의 이름, 이미지, 포트 등을 정의하여 컨테이너 생성 및 관리  
  app:  
    image: guest-book:0.0.1 # 기반이 되는 이미지 지정  
    container_name: spring-boot-guest-book # 생성될 컨테이너 이름 정의  
    ports:  
      - 8080:8080  
    depends_on: # 의존성 주입으로 해당 서비스가 먼저 생성, 실행된다  
      - mongodb  
    environment: # 환경 변수 설정.  
      - SPRING_DATA_MONGODB_URI=mongodb://jaehoonman:1234@mongodb:27017/guest_book  
  mongodb:  
    image: mongo:latest  
    container_name: mongodb-guest-book  
    environment:  
      - MONGO_INITDB_ROOT_USERNAME=jaehoonman  
      - MONGO_INITDB_ROOT_PASSWORD=1234  
    restart: always # 재시작 정책  
    ports:  
      - 27017:27017

app의 서비스에서 기반이 되는 image는 우리가 이전에 build 했던 [이미지이름]:[버전] 의 이미지를 지정해준다.

environment는 application.properties 혹은 application.yml에 지정하였던 localhost 환경을 더 이상 사용하지 못하니, docker compose를 구성하는 환경에 맞춰서 바꿔준다. 추가로 application.properties에 설정했던 host 옵션도 바꿔주자.

#spring.data.mongodb.host=localhost  
spring.data.mongodb.host=mongodb  
spring.data.mongodb.port=27017  
spring.data.mongodb.database=guest_book

옵션이 변경되었으니 이를 적용시키기 위해 build clean, build 를 통해 JAR 파일을 다시 빌드해주고, 다시

$ docker build -t [태그명]:[버전] .

의 커맨드로 docker image를 빌드해주자.

mongodb 서비스에서도 environment에 DB 접속 정보를 기입해준다.

작성이 완료되었다면 터미널을 통해 application Root 위치로 cd 하여 아래의 명령어를 입력하자.

docker compose up

Compose-working-test

app 서비스에서 depends_on 으로 지정하였던 mongodb 서비스가 먼저 구성이 되고, 이후 app이 구성되는 것을 확인할 수 있다.

생성되는 컨테이너 이름도 container_name에 맞게 구성된다.

이전과 같은 동작 테스트를 통해 정상 여부를 판단할 수 있다.


Ref.
https://velog.io/@max9106/Spring-Boot-MongoDB
https://sangmaeng.tistory.com/25
https://umanking.github.io/2023/08/04/spring-boot-mongodb/
https://yang1s.tistory.com/30
https://imasoftwareengineer.tistory.com/51