티스토리 뷰
이제 본격적으로 스프링 부트 애플리케이션을 만들어 봅시다.
전체적인 구조
구조는 기본적으로 이렇게 됩니다. 웹 브라우저가 /test라는 get을 요청하면,
TestController 클래스는 외부 요청을 분기하여 적절한 작업을 처리하고 값을 다시 반환(TestService)합니다.
이는 여느 API의 Request / Response 작업과 같습니다.
그와 붙어있는 TestService 클래스는 데이터베이스 조직이나 비즈니스 로직을 메서드 기반으로 처리하게 됩니다.
이것은 요청이 어떤 요청인지 판단하고, 그 요청에 맞는 작업을 실행하는 작업입니다.
이때 만약 Member 클래스가 있다면, MemberRepository 인터페이스가 실제 테이블과 매핑합니다.
여기서 매핑된다는 건 객체와 테이블 간 연결되어 실제 데이터를 주고 받을 수 있도록 한다는 것입니다.
이는 JPA, 하이버네이트 덕분이고, 실질적인 데이터는 h2 데이터베이스에 쌓입니다.
build.gradle에서 앱 개발에 필요한 도구를 적으면 알아서 로딩해줍니다.
그 중 대표적인 게 JPA, lombok, h2 인데,
JPA는 데이터 베이스 연결 및 구현을(ORM),
lombok은 클래스 메서드 구현 도움을(코드 자동 생성 라이브러리. 자주 사용하는 어노테이션을 자동으로 추가)
h2는 인메모리 데이터베이스를 만들어 줍니다.(Sqlite하고 비슷. 경량형 데이터베이스 생성)
즉, 장고로 따지면 model.py와 SQL ORM, Request 라이브러리 등등 되시겠다.
계층적 통신
스프링 부트는 각 계층이 양 옆의 계층과 통신하는 구조를 가집니다.
계층은 각자의 역할과 책임이 있는 어떤 소프트웨어의 구성 요소이고, 각 계층은 서로 소통할 수는 있지만 다른 계층에 직접 간섭하거나 영향을 끼치지 않습니다.
컨트롤러 - 프레젠테이션 계층(Request를 받는 계층)
서비스 - 비즈니스 계층(Views.py와 같은 함수 계층)
리포지터리 - 퍼시스턴스 계층(ORM을 다루는 계층)
데이터베이스(말 그대로 DB. 다른 계층)
컨트롤러 <-> 서비스 <-> 리포지터리 <-> 데이터베이스 간 통신 가능
하지만 영향을 미치지는 않습니다.
프레젠테이션 계층은 HTTP 요청을 받고 이 요청을 비즈니스 계층으로 전송하는 역할입니다.
대표적으로 TestController Class이고, 컨트롤러는 스프링 부트 내에 여러 개가 있을 수 있습니다.
비즈니스 계층은 비즈니스 로직을 처리합니다. 비즈니스 로직은 서비스를 만들기 위한 로직, 즉, 웹 사이트에서 발생하는 주문 데이터 처리 로직, 예외 처리 로직, 취소 로직 등 이런 서비스입니다. 서비스가 비즈니스 계층과 같다고 할 수 있습니다.
퍼시스펀스 계층은 모든 DB 관련 로직을 처리하게 됩니다. 이 과정에서 DAO 객체도 사용하며, 데이터베이스 계층과 상호작용하기 위한 객체입니다.
계층은 개념의 영역이고, 컨트롤러, 서비스, 리포지터리는 실제 구현을 위한 영역입니다.
스프링부트 디렉토리 구성하면서 이해하기
디렉토리를 구성하면서 살펴봅시다.
src > main
실제 코드를 작성하는 공간. 프로젝트 실행에 필요한 소스 코드나 리소 파일 모두가 이 폴더 안에 있다.
src > test
프로젝트의 소스 코드를 테스트할 목적의 코드나 리소스 파일이 들어 있다.
build.gradle
빌드를 설정하는 파일. 의존성이나 플로그인 설정 등과 같이 빌드에 필요한 설정을 할 때 사용(Python으로 따지면 라이브러리를 import 하는 걸 전부 미리 컴파일해서 불러온다고 생각하면 편하다)
settings.gralde
빌드할 프로젝트의 정보를 설정하는 파일(Python으로 따지면 Python을 버전 등을 설정)
Main 디렉토리 구성
main 디렉토리는 java와 resources로 기본적으로 구성되어 있다.
src > main > java / src > main > resources > static
이 중 static은 장고에서 했던 것처럼 index.html 파일만 들어 있다. static 디렉토리는 JS, CSS, 이미지와 같은 정적 파일을 넣는 용도이다. 그러므로 장고에서처럼 templates 파일을 만들고 추가해보자.
resources 폴더 위 우클릭 > New > 디렉토리 이후 templates 폴더 만들기
resources 폴더 위 우클릭 > New > 디렉토리 이후 application.yml 파일 생성(이미 있다면 만들지 않아도 됨)
application.yml 파일은 스프링 부트 설정을 할 수 있는 파일이다. 스프링부트 서버가 실행되면 자동으로 로딩되는 파일이기도 하다. 데이터베이스의 설정정보나, 로깅 설정 정보, 직접 설정 정의 등 많이 사용하므로 잊지 말고 만들어주는 게 좋다.(예를 들어 배포 할 때 어느 도메인으로 설정한다던지 하는 정보가 전부 여기에 들어간다.)
계층별 코드 추가
각 계층별 코드를 추가해보자.
일단 build.gradle에 필요한 의존성을 추가해준다.
아까 말했듯이 JPA는 ORM 라이브러리라고 생각하면 되고, H2는 Sqlite과 같은 데이터베이스를 만들어주는 라이브러리, 롬복은 어노테이션 자동 추가 등의 기능이 있는 라이브러리라고 생각하면 된다.
dependencies {
...생략 ...
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스
compileOnly 'org.projectlombok:lombok' // 롬복
annotationProcessor 'org.projectlombok:lombok'
}
여기서 implementation, runtimeOnly,compileOnly 등 어떤 의존성을 어떻게 관리할지에 대해 정하는 것이다.
하나씩 써보자면,
implementation : 프로젝트 코드(전체 코드)에서 컴파일 시점과 런타임에 모두 해당 라이브러리(JPA)를 필요로 할 때 사용된다.
testImplementation : 프로젝트의 테스트 코드를 컴파일하고 실행할 때만 필요한 의존성을 설정. 메인에서는 당연히 실행 X
runtimeOnly : 런타임에만 필요한 의존성을 지정. 컴파일 시에는 필요하지 않지만, 애플리케이션을 실행할 때 필요한 라이브러리를 설정해준다.
-> 이게 이해가 잘 안갈 수 있는데, 컴파일 시에 필요없지만 런타임에만 필요한 라이브러리 등이 있다(대표적으로 데이터베이스)
compileOnly : 반대로 컴파일할 때는 필요하지만 런타임에는 필요 없는 라이브러리를 설정한다.(이미 도움 받아서 어노테이션을 다 해줬는데 런타임에는 추가로 돌려봤자 코드 고칠 일도 없다)
annoationProcessor : 컴파일 시에 에너테이션을 처리할 때 사용하는 도구의 의존성 지정
이제 오른쪽 상단의 거북이를 클릭하면 나오는 창에서 gradle 아래의 순환 화살표 마크를 누르면 의존성이 다운로드 된다.
프레젠테이션 계층 만들기
원래 작성했던 testController.java는 이랬었다.
package me.shinsunyoung.springbottdeveloper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test")
public String test() {
return "Hello, world!";
}
}
이제 test()를 삭제하고 다음과 같이 새 코드를 추가한다.
package me.shinsunyoung.springbottdeveloper;
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;
@RestController
public class TestController {
@Autowired // TestService의 빈 주입
TestService testService
@GetMapping("/test")
public List<Member> getAllMebers() {
List<Member> members = testService.getAllMembers();
return members
}
여기서 Autowired를 사용하면 스프링 컨테이너가 자동으로 해당 필드나 생성자에 맞는 빈을 주입해준다.
빈은 싱글톤으로 관리되는 하나의 객체이다.
즉, 위에서 @Autowired라는 것으로 testService 객체와의 연결고리를 만들었다.
그리고 다음에 List<Member>로 리스트를 만들고, 해당하는 testService의 메소드를 만들었다.
그리고 해당 메소드가 객체 선언을 하여 의존성 주입을 할 수 있도록 했다.
파이썬으로 따지면 이런 느낌이다.
# Service 클래스 정의
class TestService:
def get_all_members(self):
# 여기에 실제 데이터베이스에서 멤버들을 가져오는 로직이 있다고 가정
members = [] # 빈 리스트 생성
return members
# Controller 클래스 정의
class TestController:
def __init__(self, test_service):
# 생성자를 통해 서비스 객체를 주입받음
self.test_service = test_service
def get_all_members(self):
members = self.test_service.get_all_members()
return members
# TestService 객체를 생성
test_service = TestService()
# TestController에 TestService 객체를 주입하여 생성
test_controller = TestController(test_service)
# TestController의 메서드 호출
members = test_controller.get_all_members()
print(members)
TestController라는 클래스 안에 get_all_member 메소드가 있고, 그 객체를 생성할때 이미 생성되어 있는 test_service 객체를 넣는다.
그리고 더 코드를 추가한다. TestController파일과 같은 디렉토리에 TestService.java 파일을 생성하고 만든다.
package me.shinsunyoung.springbottdeveloper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TestService {
@Autowired
MemberRepository memberRepository;
public List<Member> getAllMembers()
return memberRepository.findAll();
}
}
그러면 이렇게 된다.
서비스 어노테이션은 이 코드가 스프링의 서비스(비즈니스) 계층으로 관리된다는 걸 의미한다.
@Autowired로 MemberRepository 빈 객체가 안에 추가되어서, 데이터 베이스에 접근할 수 있게 된다.
그리고 findAll() 메서드는 ORM으로서 데이터베이스에서 가져오는 것이다.
HTTP 요청 -> TestController.java(프레젠테이션 계층)<-> TestService.java(비즈니스 계층)
즉, Views.py로 따지면 해당 /test로 요청이 되었을 때 ORM에서 데이터를 가져온다 정도가 된다.
퍼시스턴트 계층
DB에 접근할 때 사용할 객체인 Member DAO를 생성하고 실제 DB에 접근하는 코드를 추가적으로 작성한다.
Member.java 파일은 이렇다.
package me.shinsunyoung.springbottdeveloper;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = = AccessLevel.PrOTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = = GenerationType.IDENTIRY)
@Column(name = "id", updatable = false)
private long id; //DB 테이블의 'id' 컬럼과 매칭
@Column(name = "name", nullable = false)
private String name; // DB 테이블의 'name' 컬럼과 매칭
}
member라는 이름의 테이블을 만드는 작업이다. django에서는 models.py 정도라고 보면 되시겠다.
이제 매핑 작업을 할텐데, 인터페이스 파일이 필요하다. MemberRepository.java 인터페이스 파일을 만들어서 코드를 작성한다.
package me.shinsunyoung.springbottdeveloper;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이 인터페이스가 DB에서 데이터를 가져오는 퍼시스턴트 계측 역할을 하게 될 것이다.
지금은 member라는 이름의 테이블에 접근해서 Member 클래스에 매핑하는 구현체 정도로만 이해하면 된다.
Intelij에서 오류가 생겼을 때
오류 부분을 클릭해 Alt+Enter를 누른 다음 Import class를 눌러 임포트한다.
그러면 해결이 가능하다.
작동 확인하기
애플리케이션을 실행할 때 원하는 데이터를 자동으로 넣는 작업을 해야 한다.
애플리케이션을 실행할 때마다 SQL문을 실행해서 데이터베이스에 직접 넣는 번거로운 작업을 하지 않기 위해서이다.
resourece 디렉터리에 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:
jpa:
show-sql : true
properties:
hibernate:
format_sql: true
defer-datasource-initialization : true
show-sql은 어플리케이션 실행 과정에 데이터베이스에 쿼리할 일이 있으면 실행 구문을 보여주는 옵션
defer-datasource-initialization 옵션은 애플리케이션을 실행할 때 테이블을 생성하고 data.sql 파일에 있는 쿼리를 실행하도록 하는 옵션이다.
모두 수정했다면 SpringBootDeveloperApplication.java 파일 탭을 누르고 재실행 아이콘을 클릭한다.
서버 실행 후에, 콘솔창에서 Ctrl+F를 누르고 CREATE TABLE을 검색하면 테이블이 잘 만들어졌는지 볼 수 있습니다.
이제 포스트맨으로 localhost:8080/test를 하면 데이터를 확인할 수 있습니다.
포스트맨에서 데이터를 보려면 프레젠테이션 -> 비즈니스 -> 퍼시스턴스 -> 데이터 베이스 계층까지 가서 역순으로 가지고 오게 되는 것입니다.
TestControlller가 /test라는 패스에 대한 GET 요청을 처리할 수 있는 getALLMembers 메서드를 가지고 있다면, 디스패처 서블릿이 GET 요청을 처리할 수 있는 getALLMembers 메서드에 요청을 전달합니다.
그리고 getALLMembers() 메서드에서는 비즈니스 계층과 퍼시스턴스 계층을 통하면서 데이터를 가져옵니다.
그러면 뷰 리졸버가 템플릿 엔진으로 HTML 문서를 만들거나 JSON, XML 등의 데이터를 생성합니다.
그 결과로 members를 return하고 데이터를 포스트맨에서 볼 수 있게 되는겁니다.
오늘은 이렇게 스프링부트의 데이터 구조/ 계층을 이해하고, Request / Response 실습을 해보았습니다.
다음에는 스프링 부트에서의 테스트에 대해 배우겠습니다.