스프링 부트 3가 어떤 구조인지, 그리고 어떤 과정을 통해 실행되는지 코드와 함께 살펴봅니다. 여기를 공부하면 스프링 부트 3가 어떤 구조로 이루어져 있고, 앞으로 프로젝트를 진행할 때 어떤 구조로 진행해야 하는지에 대한 감을 잡을 수 있을 겁니다.
총 2편입니다. 1편에서는 스프링 부트의 구조를 살펴보고, 2편에서는 스프링 부트 프로젝트를 조금 더 발전시키면서 앞서 언급한 각 계층의 코드를 추가해보겠습니다.
[Spring] 스프링 부트 3 구조 이해하기 ❶
스프링 부트 3를 학습하기 위한 환경 설정은 [환경 설치] 되기 《스프링 부트 3 백엔드 개발자 되기 (자바편)》 참고해주세요.
2. 스프링 부트 3 프로젝트 발전시키기
스프링 부트 프로젝트를 조금 더 발전시키면서 앞서 언급한 각 계층의 코드를 추가해보겠습니다. 계층이 무엇이고 스프링 부트에서는 계층을 어떻게 나누는지 감을 조금씩 잡아가기 바랍니다. 여기서는 의존성을 추가한 다음에 프레젠테이션 계층, 비즈니스 계층, 퍼시스턴스 계층 순서대로 코드를 추가합니다.
2.1 build.gradle에 의존성 추가하기
01단계
build.gradle에 필요한 의존성을 추가해줍니다. 스프링 부트용 JPA인 스프링 데이터 JPA, 로컬 환경과 테스트 환경에서 사용할 인메모리 데이터베이스인 H2, 반복 메서드 작성 작업을 줄여주는 라이브러리인 롬복을 추가했습니다.
dependencies {
... 생략 ...
// 스프링 데이터 JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스
compileOnly 'org.projectlombok:lombok' // 롬복
annotationProcessor 'org.projectlombok:lombok'
}
02단계
오른쪽에 있는 [Gradle] 탭에서 새로고침 버튼을 누르면 앞서 추가한 의존성을 다운로드할 수 있습니다.
2.2 프레젠테이션, 서비스, 퍼시스턴스 계층 만들기
01단계
프레젠테이션 계층에 속하는 컨트롤러 관련 코드를 작성하겠습니다. 앞서 TestController.java를 작성했죠? 거기에 있던 test( ) 메서드를 삭제하고 새 코드를 추가하겠습니다.
@RestController
public class TestController {
@Autowired // TestService 빈 주입
TestService testService;
@GetMapping("/test")
public List<Member> getAllMembers() {
List<Member> members = testService.getAllMembers();
return members;
}
}
02단계
계속해서 비즈니스 계층 코드를 작성하겠습니다. TestController.java 파일과 같은 위치에 TestService.java 파일을 생성하고 아래 코드를 따라 해주세요
@Service
public class TestService {
@Autowired
MemberRepository memberRepository; // ❶ 빈 주입
public List<Member> getAllMembers() {
return memberRepository.findAll(); // ❷ 멤버 목록 얻기
}
}
❶ MemberRepository라는 빈을 주입받은 후에 ❷ findAll( ) 메서드를 호출해 멤버 테이블에 저장된 멤버 목록을 모두 가져옵니다. 지금까지 작성한 코드를 그림으로 표현하면 다음과 같습니다.
03단계
이번에는 퍼시스턴트 계층 코드를 작성하겠습니다. DB에 접근할 때 사용할 객체인 Member DAO를 생성하고 실제 DB에 접근하는 코드를 작성합니다. 같은 위치에 Member.java 파일을 생성해 다음과 같이 코드를 작성해주세요.
@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라는 이름의 테이블에 접근하는 데 사용할 객체’ 정도로만 이해하고 넘어가겠습니다. 이제 실제로 member 테이블과 Member 클래스를 매핑하는 코드를 작성하겠습니다.
04단계
매핑 작업에는 인터페이스 파일이 필요합니다. MemberRepository.java 인터페이스 파일을 새로 생성해 필요한 코드를 작성해보겠습니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이 인터페이스는 DB에서 데이터를 가져오는 퍼시스턴트 계층 역할을 할 겁니다. 자세한 설명은 《스프링 부트 3 백엔드 개발자 되기 (2판)》 ‘리포지토리 만들기’에서 다룰 것이므로 지금은 ‘member라는 이름의 테이블에 접근해서 Member 클래스에 매핑하는 구현체’ 정도로만 이해하면 됩니다.
이제 모든 계층을 구현했습니다. 혹시나 빨간 줄로 표시된 오류 메시지를 해결하지 못한 분을 위해 이 부분을 해결하는 방법을 설명하겠습니다. 대부분은 임포트 관련 오류 메시지입니다.
2.3 임포트 오류 처리하기
사실 임포트 관련 코드 입력 방법은 1장에서 설명했습니다만 처음 스프링 부트를 접하면 임포트 관련 오류 메시지를 해결하는 것이 꽤 어렵게 느껴질 수 있습니다. 여기서 다시 한번 설명하겠습니다. 앞으로는 임포트 관련 오류가 발생했을 때 이 방법으로 처리하세요.
01단계
TestService.java 파일을 열어 오류 부분을 클릭해 Alt + Enter 를 누른 다음 [Import class]를 눌러 java.util 패키지의 List 클래스를 임포트하세요. 아마 마우스 커서를 오류 부분에 올려 클릭하면 다음과 같은 안내 메시지가 나올 겁니다.
02단계
이어서 TestController.java도 같은 방법으로 해결하세요.
03단계
나머지 파일도 같은 방식으로 해결하되 같은 클래스의 이름이 여러 개 있는 경우 패키지를 선택하라고 할 수 있습니다. 그런 경우 깃허브를 참고해 임포트 오류를 해결하세요. 사실 임포트는 일일이 Alt + Enter 를 눌러 해결하면 매우 번거롭습니다. 필자는 깃허브를 참고해 임포트 오류 해결하기를 추천합니다. https://github.com/shinsunyoung/springboot-developer에 접속해보세요. 그런 다음 여러분이 보고 있는 장의 코드가 있는 위치로 찾아 들어가세요.
04단계
TestController.java 파일을 찾았으면 윗 부분에 있는 임포트 관련 코드를 복사해 붙여 넣으세요. 지금의 경우 2장을 보고 있으므로 chapter2에서 TestController.java를 찾았습니다.
💡임포트 문을 일일이 Alt + Enter 로 삽입하는 과정이 귀찮다면 [Settings → Editor → General → Auto Import → Add unambiguous imports on the fly / Optimize imports on the fly]를 찾아서 체크하고 [적용] 버튼을 눌러 설정하세요.
이렇게 하면 조금 더 쉽게 임포트 오류를 해결할 수 있습니다. 앞으로는 이 방법을 적절히 사용하기 바랍니다. 단, 이때 패키지명, 즉, 이 프로젝트에서는 me.shinsunyoung이 포함된 임포트문은 제대로 임포트되지 않으므로 me.shinsunyoung 대신 여러분이 입력한 groupId로 바꿔 입력하세요!
2.4 작동 확인하기
이제 각 계층 코드를 완성했으니 스프링 부트 애플리케이션을 실행해보겠습니다. 아직은 데이터베이스에 결과물을 볼 수 있는 데이터가 하나도 입력되지 않은 상태입니다. 보통은 이런 실행 테스트를 하기 위해 애플리케이션을 실행할 때마다 SQL 문을 실행해 데이터베이스에 직접 데이터를 넣는데요, 현재는 인메모리 데이터베이스를 사용하고 있기 때문에 애플리케이션을 새로 실행할 때마다 데이터가 사라져 매우 불편합니다. 이를 해결하기 위해 애플리케이션을 실행할 때 원하는 데이터를 자동으로 넣는 작업을 하겠습니다.
01단계
이제 애플리케이션이 실행될 때 저장할 더미 데이터를 넣을 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')
02단계
기존에 만들어놓은 application.yml 파일을 열어 아래 코드로 변경합니다. 코드에 보이는 show-sql, format_sql 옵션은 애플리케이션 실행 과정에 데이터베이스에 쿼리할 일이 있으면 실행 구문을 모두 보여주는 옵션이고, defer-datasource-initialization 옵션은 애플리케이션을 실행할 때 테이블을 생성하고 data.sql 파일에 있는 쿼리를 실행하도록 하는 옵션입니다. 모두 수정했다면 SpringBootDeveloperApplication.java 파일 탭을 누른 다음 재실행 아이콘을 클릭하세요.
💡만약 실행 버튼이 활성화되어 있지 않다면 SpringBootDeveloperApplication.java 파일 탭을 눌러보세요.
spring:
jpa:
# 전송 쿼리 확인
show-sql: true
properties:
hibernate:
format_sql: true
# 테이블 생성 후에 data.sql 실행
defer-datasource-initialization: true
03단계
솔창에서 Ctrl + F 를 누르고 CREATE TABLE을 검색해 테이블이 잘 만들어졌는지 확인합니다.
04단계
이제 포스트맨으로 HTTP 요청을 시도해보겠습니다. 포스트맨을 켜고 HTTP 메서드를 [GET]으로, URL에 http://localhost:8080/test을 입력하세요. 그런 다음 [Send] 버튼을 눌러 스프링 부트 서버에 HTTP 요청을 전송하세요. 그러면 좀 전에 data.sql 파일로 작성해 저장한 데이터를 포스트맨, 즉, 클라이언트에서 확인할 수 있습니다.
여러분이 포스트맨에서 데이터를 보기까지는 다음 그림과 같은 과정을 거칩니다. 계층과 파일을 맞춰서 과정을 한 번 확인해보세요. 앞으로 이런 과정으로 스프링이 동작한다는 감을 잡기 좋을 겁니다.
3. 스프링 부트 요청-응답 과정 한 방에 이해하기
스프링 부트로 만든 애플리케이션에서 HTTP 요청이 오면 어떤 과정을 거치며 실행되고 응답하는지 알아보겠습니다. 지금까지 뭔가 실행되는 과정들을 여러 번 이야기했지만 이 과정도 다시 봐야 합니다. 왜냐하면 스프링 부트의 전체적인 실행 과정이 어떻게 진행되는 것을 이해해야 스프링 부트로 애플리케이션을 만들었을 때 어떤 흐름으로 요청이 처리되는지 알 수 있고, 추후에 문제가 발생했을 때에도 빠르게 파악할 수 있기 때문이죠. 우선은 다음 그림을 보겠습니다.
❶ 그림을 보면 포스트맨에서 톰캣에 /test GET 요청을 합니다. 그러면 이 요청은 스프링 부트 내로 이동하는데요. ❷ 이때 스프링 부트의 디스패처 서블릿이라는 녀석이 URL을 분석하고, 이 요청을 처리할 수 있는 컨트롤러를 찾습니다. TestController가 /test라는 패스에 대한 GET 요청을 처리할 수 있는 getAllMembers( ) 메서드를 가지고 있으므로 디스패처 서블릿은 TestController에게 /test GET 요청을 전달합니다. ❸ 마침내 /test GET 요청을 처리할 수 있는 getAllMembers( ) 메서드와 이 요청이 매치됩니다. 그리고 getAllMembers( ) 메서드에서는 비즈니스 계층과 퍼시스턴스 계층을 통하면서 필요한 데이터를 가져옵니다. ❹ 그러면 뷰 리졸버는 템플릿 엔진을 사용해 HTML 문서를 만들거나 JSON, XML 등의 데이터를 생성합니다. ❺ 그 결과 members를 return하고 그 데이터를 포스트맨에서 볼 수 있게 됩니다.
학습 마무리
지금까지 스프링 부트의 기초 코드를 작성해보고 실행 원리를 공부했습니다. 스프링 부트의 프레젠테이션 계층, 비즈니스 계층, 퍼시스턴트 계층에 대해 알아보고, 스프링 부트 실행 후에 어떤 과정이 어떻게 일어나는지 알아보았습니다. 아래 핵심 요약으로 다시 정리했습니다.
핵심 요약
일반적으로 스프링 부트 프로젝트는 4개 계층으로 이루어집니다.
- 프레젠테이션 계층은 HTTP 요청을 받고 비즈니스 계층으로 전송합니다.
- 비즈니스 계층은 모든 비즈니스 로직을 처리합니다. 퍼시스턴스 계층에서 제공하는 서비스를 사용할 수도 있고, 권한을 부여하거나 유효성 검사를 하기도 합니다.
- 퍼시스턴스 계층은 모든 스토리지 관련 로직을 처리합니다. 이 과정에서 데이터베이스에 접근하기 위한 객체인 DAO를 사용할 수도 있습니다.
신선영
리멤버 백엔드 개발자. 하드 스킬과 소프트 스킬 역량을 강화하고자 부단히 공부하고 글로 남기는 백엔드 개발자입니다. 평일 기준 하루 평균 600뷰의 기술 블로그를 운영하고, 모교 학생을 대상으로 정기 세미나와 멘토링을 진행합니다. 구독자가 1,000명 정도 되는 사이드 프로젝트를 기획하고 개발하고 운영한 경험이 있습니다.
저자 블로그 shinsunyoung.tistory.com
저자 깃허브 github.com/shinsunyoung
1 Comment