FrameWork/Spring Boot

스프링부트3 구조 이해하기 (feat. 디렉토리 구성, 의존성 추가, 계층별 구현, 요청-응답 과정)

soooy0 2025. 2. 27. 16:11

  1. 웹 브라우저의 요청이 있으면, 이 요청을 **Controller클래스에서 받아 분기 처리를 하여 '어떤 요청'인지 확인하고 **Service 클래스에서 받은 요청에 맞는 작업을 실행하게 한다.(Service클래스의 메서드 호출)
  2. **Service 클래스에서는 Controller가 지정한 요청에 맞는 작업을 실행한다. 이를 위해 DB에서 데이터를 가져와야 하므로, 받은 요청에 맞는 데이터를 가져오기 위해 **Repository클래스의 메서드를 호출한다.
  3. **Repository메서드는 데이터베이스를 위한 클래스로, Member클래스로 구현한 클래스를 MemberRepository 클래스가 실제 테이블과 연결한다.
  • Controller: 프레젠테이션 계층
  • Serivce: 비즈니스 계층
  • Repository: 퍼시스턴스 계층
  • Database

이렇게 스프링 부트는 각 계층으로 이루어져 있고, 각 계층의 역할과 책임에 맞는 일을 수행하며 필요에 따라 서로 소통한다.

 

 

프레젠테이션 계층(Controller)
  • HTTP 요청을 받고, 이 요청을 비즈니스 계층으로 전송하는 역할을 한다.
  • 컨트롤러가 바로 프레젠테이션 계층이다.
  • 스프링 부트 내에 여러 개 존재 가능하다.
비즈니스 계층(Service)
  • 모든 비즈니스 로직을 처리한다.
  • 비즈니스 로직이란, 서비스를 만들기 위한 로직을 말한다.
  • 쉽게 말해 웹 사이트에서 벌어지는 모든 작업의 (예를 들면 주문 서비스의 주문 개수, 가격 등 데이터를 처리하기 위한 로직, 주문 처리를 하다가 발생하는 예외 처리 로직, 주문을 받거나 취소하는 것) 프로세스를 구현하기 위한 로직 등을 생각하면 된다.
  • 서비스가 바로 비즈니스 계층이다.
퍼시스턴스 계층
  • 모든 데이터베이스 관련 로직을 처리한다.
  • 이 과정에서 데이터베이스에 접근하는 DAO객체를 사용할 수 있다.
  • DAO는 데이터베이스 계층과 상호작용하기 위한 객체라고 이해하면 된다.
  • 레포지터리가 바로 퍼시스턴스 계층이다.

 

프로젝트 디렉터리 구성

 

  • main: 실제 코드를 작성하는 공간, 프로젝트 실행에 필요한 소스 코드나 리소스 파일은 모두 이 폴더 안에 있다.

 

  • test: 프로젝트의 소스코드를 테스트할 목적의 코드나 리소스 파일이 들어 있다.

 

  • build.gradle: 빌드를 설정하는 파일, 의존성이나 플러그인 설정 등과 같이 빌드에 필요한 설정을 할 때 사용한다.

 

  • settings.gradle: 빌드할 프로젝트의 정보를 설정하는 파일

 

 

 

 

 

 

templates 디렉터리 생성

 

  • HTML과 같은 view 관련 파일을 넣을 templates 디렉터리를 만든다. (우클릭 - New - Directory)
  • static 디렉터리는 JS, CSS, 이미지 같은 정적 파일을 넣는 용도로 사용한다.

 

 

application.yml 파일 생성

  

  • 우클릭 - new - file - application.yml 파일을 생성한다.
  • 스프링 부트 서버가 실행되면 자동으로 로딩되는 파일이다.
  • 데이터베이스의 설정 정보, 로깅 설정 정보 등이 들어갈 수 있고, 직접 설정을 정의할 때 사용하기도 한다.

 

 

 

 

build.gradle 의존성 추가
  1. 스프링부트용 JPA, 로컬 환경과 테스트 환경에서 사용할 인메모리 데이터베이스인 H2, 반복 메서드 작성 작업을 줄여주는 라이브러리 롬복 추가(우선은 데이터베이스의 테이블을 객체로 바꿔서 가져오게 도와주는 도구라고 알아두기)
dependencies {
    // implementation → 실행 시 필요한 라이브러리
    // Spring Boot 웹 애플리케이션 개발을 위한 라이브러리 추가 (내장 Tomcat 포함)
    implementation 'org.springframework.boot:spring-boot-starter-web'

    // testImplementation → 테스트 전용 라이브러리
    // JUnit 기반의 테스트 기능을 제공하는 라이브러리 추가
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // 스프링 데이터 JPA
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // 인메모리 데이터베이스, 로컬환경과 테스트 환경에서 사용함
    runtimeOnly 'com.h2database:h2'

    // 반복 메서드 작성 작업을 줄여주는 롬복 라이브러리
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

 

앞에 있는 것은 구성, 뒤에 있는 것은 의존성의 이름이다.

구성은 자바 그레이들 플러그인에서 의존성을 추가할 때 사용하는 키워드인데, 각 키워드마다 뒤에 나올 의존성을 어떻게 관리할지 정하는 것이다.

  • implementation: 프로젝트 코드가 컴파일 시점과 런타임에 모두 해당 라이브러리를 필요로 할 때 사용
  • testImplementation: 프로젝트의 테스트 코드를 컴파일하고 실행할 때만 필요한 의존성을 설정, 테스트 코드에서만 사용, 패턴 애플리케이션 코드에서는 사용하지 않음
  • runtimeOnly: 런타임에만 필요한 의존성을 지정, 컴파일 시에는 필요하지 않지만 애플리케이션을 실행할 떄 필요한 라이브러리 설정
  • compileOnly: 컴파일 시에만 필요, 런타임에는 포함되지 않아야 하는 의존성을 지정
  • annotationProcessor: 컴파일 시에 애너테이션을 처리할 때 사용하는 도구의 의존성을 지정한다.

2. 라이브러리 추가 후, 우측 Gralde 탭에서 새로고침을 누르면 의존성을 다운로드 받는다.

 

 

계층 만들기

 

1. 프레젠테이션 계층 설정, TestController.java에 이전에 작성한 test()메서드를 삭제하고, 새 코드 추가

@RestController
public class TestController {
    @Autowired
    TestService testService;
    
    @GetMapping("/test")
    public List<Member> getAllMembers(){
        List<Member> members = testService.getAllMembers();
        return members;
    }
}

 

 

2. 비즈니스 계층 설정, 같은 위치에 TestService.java 파일 생성 후 코드 작성

package me.jinsoyeong.springbootdeveloper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Member;
import java.util.List;

/*
*
* 라우터 역할을 하는 어노테이션, 라우터란? HTTP 요청과 메서드를 연결하는 장치를 의미한다.
* 이 어노테이션이 있어야 클라이언트 요청에 맞는 메서드를 매핑하여 실행할 수 있다.
*/
@RestController
public class TestController {
    @Autowired //TestService 빈 주입
    TestService testService;

    @GetMapping("/test")
    public List<Member> getAllMembers(){
        List<Member> members = testService.getAllMembers();
        return members;
    }
}

 

 

 

3. 퍼시스턴트 계층 설정, DB에 접근할 때 사용할 객체인 MemberDAO를 생성하고, 실제 DB에 접근하는 코드를 작성한다. Member.java 파일을 생성하여 코드 작성

package me.jinsoyeong.springbootdeveloper;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    private Long id; // DB 테이블의 'id' 컬럼과 매칭
    
    @Column(name = "name", nullable = false)
    private String name; // DB 테이블의 'name' 컬럼과 매칭
}

 

각 어노테이션의 역할은 나중에 공부할 에정이다. 지금은 member 라는 테이블에 접근하는 데 사용할 객체를 생성해 준 것으로만 이해하고 넘어가자.

 

 

4. 실제 member테이블과 Member 클래스를 매핑하는 코드를 작성해보자. 매핑 작업에는 인터페이스 파일이 필요하다. 따라서 MemberRepository.java 인터페이스 파일을 새로 생성해 코드를 작성한다.

package me.jinsoyeong.springbootdeveloper;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}

 

이 인터페이스는 DB에서 데이터를 가져오는 퍼시스턴트 계층 역할을 한다. 이 또한 곧 공부할 예정이니, 'member라는 테이블에 접근해서 member클래스에 매핑하는 구현 단계' 정도로 이해하자.

 

 

작동 확인

 

원래는 애플리케이션 실행 전 SQL문을 실행해 데이터베이스에 직접 데이터를 넣었으나, 현재는 인메모리 데이터베이스를 사용하고 있기 때문에 애플리케이션을 새로 실행할 때마다 데이터가 사라져 매우 불편하다.

 

이를 해결하기 위해 애플리케이션을 실행할 때는 원하는 데이터를 자동으로 넣는 작업을 해보자.

 

애플리케이션이 실행될 때 저장할 더미데이터를 넣을 SQL 파일 생성, resources 디렉토리 -> data.sql 파일 생성, 코드 작성
INSERT INTO member (id, name) VALUES (1, 'name 1')
INSERT INTO member (id, name) VALUES (2, 'name 2')
INSERT INTO member (id, name) VALUES (3, 'name 3')

 

 

application.yml 파일에 코드 작성
spring:
  # YAML은 들여쓰기를 사용하여 계층 구조를 나타낸다. 일관되게 사용하여 꼭 들여쓰기에 유의할 것!
  jpa:
    # 전송 쿼리 확인
    show-sql: true # 애플리케이션 실행 과정에 데이터베이스에 쿼리할 일이 있으면, 실행 구문을 모두 보여주는 옵션
    properties:
      hibernate:
        format_sql: true  # 애플리케이션 실행 과정에 데이터베이스에 쿼리할 일이 있으면, 실행 구문을 모두 보여주는 옵션

    # 애플리케이션 실행할 때 테이블을 생성하고 data.sql 파일에 있는 쿼리를 실행
    defer-datasource-initialization: true

이러한 계층 구조이다.

 

SpringBootDeveloperApplication.java 파일탭을 누른 후 서버 재실행 아이콘 클릭
cmd + F 를 누르고,CREATE TABLE을 검색해 테이블이 잘 만들어졌는지 확인한다.

 

 

postman으로 HTTP요청 시도해보기

 

 

1. 포스트맨을 켜서 HTTP 메서드를 GET으로 설정하고, URL에 http://localhost:8080/test 를 입력한다.

2. send버튼을 눌러서 스프링 부트 서버에 HTTP 요청을 전송한다.

3. 그러면 좀 전에 data.sql파일로 작성해 저장한 데이터를 포스트맨(클라이언트)에서 확인 할 수 있다.

 

 

스프링부트의 요청-응답과정

 

1. 포스트맨에서 톰캣에 /test GET 요청을 보낸다. 그러면, 이 요청은 스프링 부트 내로 이동한다.

  • 이때, 스프링부트의 dispatcherServlet이 URL을 분석하고, 이 요청을 처리할 수 있는 Controller를 우선적으로 찾는다.
  • 우리가 이전에 만들었던 TestController가 /test 라는 패스에 대한 GET 요청을 처리할 수 있는 getAllMembers() 메서드를 가지고 있었다. 따라서 dispatcherServlet이 TestController에게 /test GET 요청을 전달한다.

2. 마침내 /test GET 요청을 처리할 수 있는 getAllMembers()메서드가 매치된다.

  • getAllMembers()메서드에는 비즈니스 계층(service)과 퍼시스턴스 계층(memberRepository)를 통하면서 필요한 데이터를 가져오게끔 코드를 구현해놓았기 때문에, 위 계층들을 통하며 필요한 데이터를 가져온다.

3. view resolver(뷰 리졸버)는 템플릿 엔진을 사용해 HTML을 만들거나 JSON, XML 등의 데이터를 생성한다.

4. 그 결과 members를 return하고 그 데이터를 포스트맨에서 볼 수 있게 된다.

 

 

[정리]
1. 포스트맨에서 HTTP 요청을 톰캣에게 보낸다.
2. 디스패처 서블릿에서 해당 요청을 받아 적절한 컨트롤러를 선택한다.
3. 프레젠테이션 계층에서 서비스 계층과 퍼시스턴스 계층을 통하면서 필요한 데이터를 가져온다.
4. view resolver(뷰 리졸버)는 템플릿 엔진을 사용해 HTML을 만들거나 JSON, XML 등의 데이터를 생성한다.
5. 디스패처 서블릿에 의해 응답으로 클라이언트에게 반환된다.