백엔드/Spring Boot

3. 스프링 부트 3 구조 이해하기

두개의 문 2023. 7. 12. 21:54

1. 스프링 부트 3 구조 살펴보기 

  - 계층

   : 각자의 역할과 책임이 있는 어떤 소프트웨어의 구성 요소를 의미 

     → 각 계층은 소통 가능하지만, 직접 간섭하거나 영향을 미치지 않음 

 

 

1) 스프링 부트의 계층 알아보기 

 

  프레젠테이션 계층 <---> 비즈니스 계층 <---> 퍼시스턴스 계층 <---> 데이터베이스 

         컨트롤러                          서비스                        레포지토리  

   

   

   ◉ 프레젠테이션 계층

    • HTTP 요청을 받고 이 요청을 비즈니스 계층으로 전송하는 역할 → 컨트롤러

    • 컨트롤러는 스프링 부트 내에 여러 개가 있을 수 있음 

     

   ◉ 비즈니스 계층 

    • 모든 비즈니스 로직을 처리 → 서비스 

 

   ◉ 퍼시스턴스 계층 

    • 모든 데이터베이스 관련 로직을 처리 → 레포지토리 

    • 이 과정에서 데이터베이스에 접근하는 DAO 객체 사용 가능 

      → DAO : 데이터베이스 계층과 상호작용하기 위한 객체

 

 ⇒ 계층 : 개념의 영역

     컨트롤러, 서비스, 리포지토리 : 실제 구현을 하는 영역 

 

 


2) 스프링 부트 프로젝트 디렉터리 구성하며 살펴보기 

 

 ◉ main 

   - 실제 코드를 작성하는 공간

      → 프로젝트 실행에 필요한 소스 코드나 리소스 파일은 모두 이 폴더 안에 존재 

 

 ◉ test 

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

 

 

 ◉ build.gradle 

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

 

 

 ◉ setting.gradle

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

 

 


3) main 디렉터리 구성하기 

 

   ① /resource/templates : HTML과 같은 뷰 관련 파일
   ② /resource/static : JS, CSS, 이미지와 같은 정적 파일

   ③ /webapp/WEB-INF/view : JSP 파일 

   ④ /resource에 application.properties 파일 대신 application.yml 파일 생성 ( 확장자 변경하면 됨 ) 

      → 스프링 부트 서버가 실행되면 자동으로 로딩되는 파일 ( 데이터베이스의 설정 정보, 로깅 설정 정보 등 )

         : application.yml의 경우, application.properties에 비해 중복되는 코드는 제거하므로 가독성 높으며 설정파일의 오류 찾기 쉬움 

         • 중복되는 코드 삭제 / 들여쓰기  

         • '=' 대신 ':'(콜론)

          주석 : '#' 

 

   


2. 스프링 부트 3 프로젝트 발전시키기 

◉ 의존성 추가 → 프레젠테이션 계층 → 비즈니스 계층 → 퍼시스턴스 계층 순으로 코드 작성

 

 

1) build.gradle에 의존성 추가하기 

 

① build.gradle에 필요한 의존성 추가 

 ▪︎ 스프링 부트용 JPA인 스프링 데이터 JPA(Java Persistence API)

   : 자바 객체와 데이터베이스를 연결해 데이터를 관리 / 객체 지향 도메인 모델과 데이터베이스의 다리 역할 

 ▪︎ 로컬 환경과 테스트 환경에서 사용할 인메모리 데이터베이스인 H2

 ▪︎ 반복 메서드 작성 작업을 줄여주는 라이브러리인 롬복 

 

② 의존성 추가 후, 반드시 Refresh Gradle Project 해주기 


▪︎ 내장형 DB(In-memory DB)란?

- 디스크 베이스 DB와 반대로 메모리에 데이터를 저장하는 것을 인메모리 DB라고 함 

- 외부 저장 장치에 데이터를 저장하지 않고 메모리에서 데이터를 읽고 쓰기 때문에 디스크 베이스 DB에 비해 데이터 이용 속도가 빠른 편

 

- H2

  • 자바 기반의 오픈소스 RDBMS

  • 브라우저 기반의 콘솔 모드를 이용할 수 있으며 설치과정이 불필요 

  • 인메모리 DB를 지원하며 디스크 테이블도 생성 가능 

  • 내장모드 & 서버모드 모두 지원

  • 가성비가 좋으나, 안정성 문제로 소규모 프로젝트의 테스트용으로 사용 

 

 [ 참고자료 ]

https://cyberx.tistory.com/308 

 

 


2) 프레젠테이션, 서비스, 퍼시스턴스 계층 만들기 

 

 

◉ 프레젠테이션 계층  - LayerController 생성 

package org.choongang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody	
// 위의 두 가지 애너테이션이 합쳐진 것이 '@RestController' 
public class LayerController {
	@Autowired 	// → 멤버 변수에 의존성 주입을 통해 초기화시킴 
			// 스프링 부트 컨테이너가 LayerService 객체(빈)를 만들어 제공함 
	LayerService layerService;
	
	@GetMapping("/hello")		// 웹 브라우저가 '/hello'로 요청을 하면, 
					// getAllMembers() 메서드가 처리함 
	public List<Member> getAllMembers() {
		List<Member> members = layerService.getAllMembers();
		// 서비스로부터 데이터를 리스트형태로 받아옴 
		return members;
	}
}

❶ @RestController 

  ▪︎ 라우터 역할로,  @Controller 애너테이션에 @ResponseBody 애너테이션이 합쳐진 결과물

   - @ResponseBody : HTTP 요청 Body를 자바 객체로 전달 받을 수 있음 

  ▪︎ @RestController 

    : @Controller와 다르게 리턴값에 자동으로 @ResponseBody가 붙게 되어 별도 어노테이션을 명시해주지 않아도 HTTP 응답데이터(Body)에 자바 객체가 매핑되어 전달됨 

  ▪︎ @Controller의 경우, Body를 자바 객체로 받기 위해서는 @ResponseBody 어노테이션을 반드시 명시해주어야 함 

  

❷ @Autowired

 - 필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입함 

 - 다음 3가지 경우에 @Autowired 어노테이션 사용 가능 

  • 생성자 ( 생성자를 통해 의존성 주입하는 것을 권장 ) 

  • setter

  • 필드 

 


 ▪︎ @RequestBody /@ResponseBody 정리

  - 클라이언트에서 서버로 필요한 데이터를 요청하기 위해 JSON 데이터를 요청 본문에 담아서 서버로 보내면,

     서버에서는 @RequestBody 어노테이션을 사용하여 HTTP 요청 본문에 담긴 값들을 자바 객체로 변환시켜, 객체에 저장함 

  - 서버에서 클라이언트로 응답 데이터를 전송하기 위해 @ResponseBody 어노테이션을 사용하여 자바 객체를 HTTP 응답 본문의 객체로 변환하여 클라이언트로 전송함 

 

[ 참고 자료 ]

https://cheershennah.tistory.com/179


비즈니스 계층 - LayerService 생성 

 

 - MemberRepository라는 빈을 주입받은 후에, findAll( ) 메서드를 호출해 멤버 테이블에 저장된 멤버 목록을 모두 가져옴 

package org.choongang.service;

import java.util.List;
import org.choongang.domain.Member;
import org.choongang.domain.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LayerService {
	
	@Autowired 
	MemberRepository memberRepository;	// ❶ 빈 주입 
    
	// * 비즈니스 로직 처리 메서드로, 모든 회원 명단을 가져오게 됨 
	public List<Member> getAllMembers() {
		return memberRepository.findAll();	// ❷ 멤버 목록 얻기 
	} 
}

▪︎ @Service 

 - 비즈니스 로직 처리 

 - @Service 애너테이션 : @Component 포함 → 해당 클래스를 스프링 컨테이너에 빈 객체로 생성하는 역할 

 - 서비스의 역할 

   • 유동적으로 변하는 데이터를 정제하는 작업 수행 

 

▪︎ 데이터베이스와의 작업은 모델에서 수행 → Repository 객체가 필요함 

▪︎ @Autowired 애너테이션을 사용해 스프링 컨테이너가 MemberRepository 객체(빈) 생성 및 관리 

  → 스프링 컨테이너가 생성된 MemberRepository 객체를 서비스에게 전달해줌 

 

▪︎ Repository 객체의 findAll() 메서드를 이용해 모든 데이터를 가져옴 

  → 이 메서드는 JpaRepository 인터페이스에 구현되어 있음 ( 상속받고 사용하면 됨 ) 

  - 실제 구현은 Hibernate 객체가 구현함 

  - 스프링 부트에서 데이터베이스 처리 

    : JPA → MyBatis → JDBC 순대로 진행 

   • 기본 JPA : SQL 필요없이 JPA가 자동으로 자동으로 생성하고 사용함  

   • 기본 MyBatis : SQL만 별도 설정파일에 기술해주면 MyBatis 라이브러리가 메서드를 자동 생성함 

   • 기본 JDBC : 모든 데이터 베이스 코드르 직접 다루어야 함 (제일 코딩이 어려운 편 )

 

 


◉ 퍼시스턴스 계층 - Member 생성 

 

 - DB에 접근할 때 사용할 객체인 Member DAO를 생성하고, 실제 DB에 접근하는 코드 작성 

package org.choongang.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access=AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter	// 데이터가 한번 만들어지면 고칠 수 없도록 설정 (VO 형식)
@Entity // 모델 클래스와 데이터 클래스를 1:1로 동기화함 
	// 데이터베이스 테이블의 구조와 동등 -> JPA가 만듦 
public class Member {
	@Id		
    	// 일련번호로 Primary Key를 사용함 
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	// GeneratedValue : 자동생성된다는 의미 
	// strategy : 만드는 방식, 전략 -> 자동으로 일련번호를 만들어서 사용하라 
	@Column(name="id", updatable=false)
	private Long id;
	// 테이블의 'id' 컬럼과 매핑 
	
    @Column(name="name", nullable=false)
	private String name; 
}

▪︎ @Id

 - Long 타입의 id 필드를 테이블의 기본키로 지정 

 

▪︎ @GeneratedValue

 - 기본키의 생성 방식 결정 

 - strategy : 만드는 방식 지정 

   • IDENTITY : 기본 키 생성을 데이터베이스에 위임 ( = AUTO_INCREMENT ) 

 

▪︎ @Column

 - 보통 해당 테이블의 컬럼명과 다른 멤버변수 이름으로 사용하고 싶은 경우에 사용 

 - 주로 테이블의 컬럼명의 경우, 스네이크 표기법 선호 ( 밑줄 _ 사용 )

   자바의 경우, 대소문자의 카멜표기법 선호 

   ⇒ 두 명칭이 표기법으로 인해 불일치할 경우, JPA가 해당 컬럼을 연결하지 못하는 상황 발생 

 - 따라서, 명시적으로 @Column 애너테이션을 사용하여 매핑해줌 

   동시에 중요 속성이 있는 경우에도 사용함 ( 추후 배울 예정, 우선 위의 코드에 나온 것만 정리해보자 )

   ❶ name : 필드와 매핑할 컬럼 이름. 설정하지 않으면 필드 이름으로 지정해줌.

   ❷ nullable : 컬럼의 null 허용 여부. 설정하지 않으면 true(nullable)

   ❸ updatable : 컬럼의 update 허용 여부. 

 

 

⇒ member 테이블에 접근하는 데 사용하는 객체 정도로 일단 이해하고 넘어가자 

 

 


◉ 매핑 작업 - MemberRepository 인터페이스 필요 

 

  - 실제로 member 테이블과 Member 클래스를 매핑하기 위해서는 인터페이스 파일이 필요함 

    → MemberRepository.java 인터페이스 파일을 생성해보자 

  - DB에서 데이터를 가져오는 퍼시스턴트 계층 역할   

     즉, member 테이블에 접근해서 Member 클래스에 매핑하는 구현체

package org.choongang.domain;

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

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

}

 


3. 임포트 오류 처리하기 

 

◉ 임포트 순서 : 내가 만든 클래스 → java → javax → jakarta 순

 

 


4. 작동 확인하기 

 

◉ 각 계층의 코드 완성했으니, 스프링 부트 애플리케이션을 실행해보자 

 

- 현재는 결과물을 볼 수 있는 데이터가 데이터베이스에 하나도 입력되지 않은 상태 

- 보통 이런 실행 테스트를 위해 애플리케이션을 실행할 때마다 SQL문을 실행해 데이터베이스에 직접 데이터를 넣음 

- 지금은 인메모리 데이터베이스인 H2를 사용 → 애플리케이션을 새로 실행할 때마다 데이터가 사라져 매우 불편함 

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

 

 

 ① 애플리케이션이 실행될 때 저장할 더미 데이터를 넣을 SQL 파일을 생성해보자

    : jsp 파일로 생성한 , INSERT 추가 → webapp 있는 jsp파일을 resources 폴더로 옮긴 , 확장자를 .sql 변경 

 

INSERT INTO member (id, name) VALUES (1, '구름');
INSERT INTO member (id, name) VALUES (2, '하늘');
INSERT INTO member (id, name) VALUES (3, '산');
INSERT INTO member (id, name) VALUES (4, '바다');
INSERT INTO member (id, name) VALUES (5, '비');

 

 ▪︎ 더미 데이터(Dummy Data)란?

  - 유용한 데이터가 포함되지는 않지만, 공간을 예비해두어 실제 데이터가 명목상 존재하는 것처럼 다루는 정보 

  - 언제 사용할까? 

     여러 건의 물리적 데이터를 이용하여 테스트할 때 사용 

 

 

② application.yml 파일의 코드 변경 

server : 
  port: 80

spring :  
  mvc:                          # spring.mvc
    view:                       # spring.mvc.view
      prefix: /WEB-INF/view/    # spring.mvc.view.prefix=/WEB-INF/view/
      suffix: .jsp              # spring.mvc.view.suffix=.jsp
#  datasource:                   
#    driver-class-name: oracle.jdbc.OracleDriver 
#    url: jdbc:oracle:thin:@localhost:1521/xe
#    username: webuser
#    password: webuser 

  jpa:
    # 실제 데이터베이스로 전송되어 처리되는 쿼리 내용을 보여라  
    show-sql: true
    properties:
      hibernate:
        # jpa 구현 hibernate에서 sql를 형식 정렬하여 사용하라 
        format_sql: true 
        
    # 테이블 생성 후에 data.sql 실행 
    # 메모리 데이터베이스를 사용하면 스프링 부트 앱을 실행할 때마다
    # 모든 데이터를 지워버리고 다시 만든다. 그 때 기초 데이터로 사용하기 위해
    # 지정한 sql 파일을 사용하라는 뜻 
    # schema.sql : 테이블 구조 만들기 - DDL
    # data.sql : 테이블에 데이터 추가 등 - DML  
    defer-datasource-initialization: true

 ❶ show-sql, format_sql 옵션 : 애플리케이션 실행 과정에서 데이터베이스에 쿼리를 할 일이 있으면 실행 구문을 모두 보여주는 옵션

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

 

 

③ 실행해보자 

 : Console창에서 오른쪽 클릭 - Find/Replace 클릭 : ‘CREATE TABLE’ 검색 

 

 

 

④ 포스트맨에서 HTTP 요청 시도 

  - HTTP 메서드 : GET / URL : http://localhost:80/hello → [ Send ] 클릭해 스프링 부트 서버에 HTTP 요청 전송 

 

 

 


3. 스프링 부트 요청 - 응답 과정 한 방에 이해하기 ★★★

 

◉ 스프링 부트의 전체적인 실행과정 

 

    HTTP 요청

--------------> LayerController.java <---> LayerService.java <---> MemberRepository.java <---> DB

 

    url:/hello             프레젠테이션 계층                           비즈니스 계층                              퍼시스턴스 계층

 

① 포스트맨에서 톰캣에 '/hello'로 GET 요청 → 스프링 부트 내로 요청 이동 

② 스프링 부트의 Dispatcher Servlet이 URL 분석 후, 이 요청을 처리할 수 있는 컨트롤러 찾음  

    → LayerController가 '/hello'라는 경로에 대한 GET 요청을 처리할 수 있는 getAllMembers( ) 메서드를 가지고 있으므로, Dispatcher Servlet가 해당 컨트롤러에게 요청을 전달함  

③ getAllMembers( ) 메서드에서는 비즈니스 계층과 퍼시스턴스 계층을 통하면서 필요한 데이터를 가져옴 

④ View Resolver는 템플릿 엔진을 사용해 HTML 문서를 만들거나 JSON, XML 등의 데이터를 생성함 

⑤ 그 결과, members를 return하고 그 데이터를 포스트맨에서 볼 수 있음 

 

 

 


 

▪︎ Dispatcher Servlet란?

  HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러(Front Controller)

  해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리면서 상당히 편리하게 이용

 

  • Dispatcher Servlet의 처리 과정 

   ❶ 클라이언트로부터 어떠한 요청이 오면 Tomcat(톰캣)과 같은 서블릿 컨테이너가 요청을 받음 

       ( 모든 요청은 프론트 컨트롤러인 Dispatcher Servlet이 가장 먼저 받게 됨 )

   ❷ 디스패처 서블릿은 공통적인 작업을 먼저 처리한 후에 해당 요청을 처리해야 하는 컨트롤러를 찾아서 작업을 위임

 

 • Dispatcher Servlet의 장점 

   : 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주며, 공통 작업을 처리 

     → 컨트롤러를 구현해두기만 하면, Dispatcher Servlet가 알아서 적합한 컨트롤러로 위임해주는 구조 

 

 

 - Front Controller(프론트 컨트롤러)란?

  • 주로 서블릿 컨테이너의 제일 앞에서 서버로 들어오는 클라이언트의 모든 요청을 받아서 처리해주는 컨트롤러

  • MVC 구조에서 함께 사용되는 디자인 패턴  

 

 

[ 참고 자료 ]

https://mangkyu.tistory.com/18

   


▪︎ 템플릿 엔진(Template Engine)이란?

  - 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어 

    → 웹 사이트 화면을 어떤 형태로 만들지 도와주는 양식 

  - view code(HTML)와 data logic code(DB Connection)를 분리해주는 기능을 함 

  - 템플릿 엔진은 서버 사이드 템플릿 엔진과 클라이언트 사이드 템플릿 엔진으로 분류됨 

 

  ① 서버 사이드 템플릿 엔진 (Server Side Template Engine)

   • 서버에서 DB 혹은 API에서 가져온 데이터를 미리 정의된 Template에 넣어 HTML를 그려서 클라이언트에 전달해주는 역할 

   • 대표적인 서버 사이드 템플릿 엔진 : Thymeleaf, JSP, Freemarker

  ② 클라이언트 사이드 템플릿 엔진 (Client Side Template)

   • HTML 형태로 코드를 작성할 수 있으며, 동적으로 DOM를 그리게 해주는 역할 

   • 데이터를 받아서 DOM 객체에 동적으로 그려주는 프로세스를 담당 

   • 대표적인 클라이언트 사이드 템플릿 엔진 : Mustache, Squirrelly, Handlebars

 

https://velog.io/@hi_potato/Template-Engine-Template-Engine