- 5주차 실습과 6주차 Layer 강의를 들으며 작성한 글이다 !
DB 를 적용하지 않은 시점이라는 걸 주의하고 보아야 한다.
1️⃣ 기본 개념
일단 우리는 5가지의 Package 를 만들 것이다.

먼저 각각의 역할에 대해서 알아보겠다.
1. Entity
- 데이터 모델을 정의하는 클래스이다.
- DB 테이블과 매핑(연결)되는 객체이다.
2. Dto (데이터 전송객체, Data Transfer Object)
- 클라이언트와 서버 간 데이터 전송을 담당한다.
3. Controller (컨트롤러)
- 클라이언트의 요청을 받고
> 요청에 맞는 서비스 메소드를 호출해서,
> 서비스 계층의 처리 결과를 클라이언트에 응답하는 역할을 한다.
4. Service (서비스)
- 비즈니스 로직을 처리하는 계층
- 컨트롤러에서 받은 데이터를 가공하거나 연산을 수행한 후 다른 계층에 전달
5. Repository (레포지토리)
- 데이터베이스와 직접 연결되어 데이터를 저장하고 조회하는 계층
2️⃣ 은행 예시
이번에는 은행이라는 예시를 들어서 그림으로 어떤 동작이 흐르는지 이해해보겠다.
1. Entity는 통장

- 계좌 정보가 저장된 통장을 우리는 Entity 라고 할 것이다.
2. 고객 요청 받기

- 은행에서 고객이 직원한테 30,000원 입금을 해달라 했다. 그렇다면 은행 직원은 해당 요청을 은행 내부 시스템에 입력할 것이다.
- 이때 은행 직원은 Controller 의 역할과 비슷하다고 하면 된다.
3. 요청 받은 부분을 내역서로 작성하기

- 우리는 요청 받은 Request Dto 를 받은 것이다.
- 여기에는 Entity 처럼 모든 정보가 담긴 것이 아닌, 필요 정보만 넘기는 것이다.
4. 내역서를 처리하기

- 우리는 일치하는 내역서 속 통장을 찾기 위해서 은행 내부 시스템이 작동할 것이다.
- 일치하는 계좌를 찾아서 그 계좌 속 돈을 바꾸는 일도 처리할 것이다.
- 이렇게 실제 데이터를 가져와서 처리하는 걸 우리는 Service 계층이라고 한다.
- Entity와 같이 모든 정보를 넘겨주는 것이 아닌, 필요 정보만 넘겨준다 : >
5. Repository

- 실제 데이터이다.
- 서비스 로직이 원했던 데이터를 반환해주는 역할을 한다.
3️⃣ 사전 작업
Memo 를 CRUD 한다는 서버가 있다고 해보자. 그때의 사전작업을 한번 보도록 하자.
Memo 를 Create 할려면 먼저 Memo 라는 걸 담아낼 객체가 필요하다.
우리는 이 것을 Entity 라는 객체에 담아낼 것이다.
1. Entity
@Getter
@AllArgsConstructor
public class Memo {
// filed 를 정의한다
@Setter
private Long id;
private String title;
private String contents;
// title 과 contents 만 받을 생성자
public Memo(String title, String contents) {
this.title = title;
this.contents = contents;
}
}
Entity 를 만들었으면 그 다음은 ? 우리가 이 객체를 담을 수 있는 것을 만들어야한다.
왜 근데 Memo를 담기 위해 Entity를 만들고, 또 Entity 를 담기 위해 또 객체를 만들어하는가? 생각이 들수있다.
이 부분은 보안, 데이터 무결성, 유지보수 문제가 있어서 하나의 박스 즉, DTO에 데이터를 담아 보내는 것이다.
일단 Entity 라는 것은 실제 데이터와 1:1 매칭(연결) 되어 있는 객체이다.
우리가 이것을 전달받고, 수정하면 실제 데이터를 수정하는 것과 같은 것이다. 이렇게 되면 우리는 원본 데이터를 가공하게 되는 것이다. 그리고, 이 원본데이터를 그대로 클라이언트에 보내게 되면 비밀번호와 같은 중요 정보가 같이 전송될 수 있다.
그래서 우리는 이러한 문제점을 DTO 라는 객체를 통해서 특정요청이나 응답에서 필요한 데이터만 담아낼 것이다.
2. DTO
DTO 에서는 우리가 요청을 받은 DTO 와 우리가 응답을 줄 DTO 두 개가 있다.
이게 조금 이해가 안 될 수 있는데, 위의 예시를 다시 이용해 보자면
"1. 내 이름은 ㅇㅇㅇ이고 계좌번호는 이거야. 내 계좌 잔액을 찾아줘 " 는 요청을 받은 DTO 이고,
" 2. 너의 이름은 ㅇㅇㅇ 이고, 잔액은 ㅇㅇㅇ 있어." 이거는 응답을 주는 DTO 이다.
우리는 그래서 이러한 요청 데이터와 응답 데이터를 각각 만들 것이다.
1) MemoRequestDto
import lombok.Getter;
@Getter
public class MemoRequestDto {
private String title;
private String contents;
}
- 우리가 요청 받은 데이터에는 ID 값이 주어지지 않았을 것이다.
2) MemoResponseDto
@Getter
public class MemoResponseDto {
private Long id;
private String title;
private String contents;
public MemoResponseDto(Memo memo) {
this.id = memo.getId();
this.title = memo.getTitle();
this.contents = memo.getContents();
}
}
- 우리가 줄 데이터는 실제 DB 에서 Entity를 받아올 것이다. 그렇기 때문에 생성자가 Memo memo 라는 Entity 를 받는 걸 알 수 있다.
3. Controller
- 우리가 DTO 를 통해서 주고 받아주는 역할을 해줄 Controller 도 필요할 것이다.
@RestController
@RequestMapping("/memos")
public class MemoController {
private final MemoService memoService;
}
- @RestController 는 데이터를 반환해준다는 것이다. Controller 어노테이션은 View 를 반환해준다. 일단 큰 차이점은 둘은 데이터를 응답하는 방식에서 차이가 있는 것이다.
@ Controller 는 View(HTML, JSP 등)를 반환하는 컨트롤러이다. 일반적인 웹 애플리케이션에서 HTML 페이지를 응답할 때 사용한다. 주로 Thymeleaf, JSP 같은 템플릿 엔진과 함께 사용한다.
@RestController는 데이터(JSON, XML)를 반환하는 컨트롤러이다. API 서버에서 주로 사용되며, View를 반환하지 않고 데이터만 응답한다. @Controller + @ResponseBody 와 같은 역할을 한다 !
- @RequestMapping 은 "/memos" 라는 공통된 url 을 가지겠다는 것이다.
4. Service
- Controller 가 보낸 요청 데이터를 처리할 수 있는 Service 계층이 필요하다.
- Service 패키지에는 Service 라는 인터페이스와 Service 인터페이스를 구현한 구현체 두가지 만들 것이다.
- 두개로 구분해서 만드는 이유는 인터페이스를 사용하면 구현체를 변경하거나 추가할 때 코드 수정이 최소화한다.
- 추가적으로 Spring 에서는 의존성 주입(DI, Dependency Injection) 을 활용해서 객체 간 결합도를 낮추는 게 중요한데, 이렇게 인터페이스를 두게되면 Service 를 사용하는 객체가 특정 구현체에 의존하지 않도록 만들 수 있다.
- 인터페이스를 의존하면 유지보수와 확장성이 높아지고, 컨트롤러를 수정할 필요가 없다.
- 구현체를 직접 사용하면 변경이 어렵고, 테스트하기도 불편하다.
- 그래서 대부분의 경우 인터페이스를 만들고, 이를 구현하는 방식이 더 좋은 설계이다!
1) MemoService
package com.example.layered.service;
import com.example.layered.dto.MemoRequestDto;
import com.example.layered.dto.MemoResponseDto;
import java.util.List;
public interface MemoService {
}
2) MemoServiceIml
public class MemoServiceImpl implements MemoService {
private final MemoRepository memoRepository;
}
5. Repository
- 여기에는 실제 데이터를 저장하고 관리하는 곳이다.
- 여기서도 똑같이 인터페이스와 인터페이스를 구현한 구현체 두 가지로 구성할 것이다.
- 위의 서비스 계층과 비슷한 이유로 인터페이스를 사용하면 유지보수성이 좋아지고, 구현체를 바꿔도 코드 변경이 최소화된다.
1) MemoRepository
public interface MemoRepository {}
2) MemoRepositoryImpl
public class MemoRepositoryImpl implements MemoRepository {
private final Map<Long, Memo> memoList = new HashMap<>();
}
그림으로 다시 이해해보자.
6. 전체적인 흐름





- 이 개념이 영속성 컨텍스트 이다. 영속성 컨텍스트란 " JPA에서 Entity를 관리하는 일종의 1차 캐시(메모리 저장소) " 이다.
- 영속성 컨텍스트에 저장되면 특별한 관리 대상이 된다. DB와 연결된 상태에서 엔티티의 변경을 감지하고 자동으로 반영해주는 역할을 한다.


4️⃣ Create
메모 생성 과정을 구현해 볼 것이다.
1) Controller
먼저 Controller 에서 Post Mapping 을 하여야 한다.
@PostMapping
public ResponseEntity<MemoResponseDto> createMemo(@RequestBody MemoRequestDto dto) {
return new ResponseEntity<>(memoService.saveMemo(dto), HttpStatus.CREATED);
}
이때, 우리는 ResponseEntity 라는 HttpStatus 로 함께 반환해줄 수 있는 객체를 반환값으로 할 것이다.
@RequestBody는 클라이언트가 보낸 JSON 데이터를 자바객체로 변환해주는 어노테이션이다.
즉, HTTP 요청의 본문(body)에 담긴 데이터를 dto 로 매핑하는 역할을 한다.
그러니까 요약하면 아래와 같다.
우리는 클라이언트가 보내준 JSON 데이터를 dto 로 매핑하여 파라미터로 받고,
Response dto 를 HttpStatus 와 함께 반환해줄 것이다.
그리하여, 우리는 매핑한 Request dto 를 Service 로직에 보내주게 되는 것이다. 이 로직이 성공하면 우리는 Created 라는 상태를 반환할 것이다 !
2) Service
MemoResponseDto saveMemo(MemoRequestDto dto);
- 서비스 인터페이스에 위와 같이 메서드를 선언해둔다. 그 후 구현체에 이 메서드를 구현할 것이다.
@Override
public MemoResponseDto saveMemo(MemoRequestDto dto) {
// 요청받은 데이터로 Memo 객체 생성 ID 값은 없음
Memo memo = new Memo(dto.getTitle(), dto.getContents());
// DB 에 저장
Memo savedMemo = memoRepository.saveMemo(memo);
return new MemoResponseDto(savedMemo);
}
- 우리는 새로운 메모라는 엔티티를 만들것이다. 그곳에는 우리나 요청받은 dto 의 이름과 내용을 저장해 둘것이다.
- 그런 후 그 엔티티를 레퍼지토리에 저장할 것이다.
- 그런 후 저장된 엔티티를 Response DTO 에 담아 보낼 것이다.
- 이 때, Repository 랑 연결이 되어야 한다.
public class MemoServiceImpl implements MemoService {
private final MemoRepository memoRepository;
public MemoServiceImpl(MemoRepository memoRepository) {
this.memoRepository = memoRepository;
}
}
- 이런식으로 final 로 선언 해주며, 생성자를 만들어주어야 한다. 왜나하면 " Spring의 의존성 주입(Dependency Injection, DI)을 적용하기 위함 " 이다.
① Spring에서는 객체 간의 의존 관계를 자동으로 해결해 주는데, 이를 위해서는 MemoServiceImpl이 MemoRepository에 의존하고 있다는 사실을 명시적으로 알려줘야 한다.
② 생성자 주입: MemoServiceImpl 클래스는 MemoRepository를 의존하고 있다는 것을 명확히 나타낸다. Spring은 해당 클래스가 생성될 때 MemoRepository 객체를 자동으로 주입하게 된.
- > 불변성, 명확한 의존관계, 컴파일 시점 검증 과 같은 장점이 있다.
3) Repository
- Service 와 마찬가지로 인터페이스와 그 구현체로 나눌 것이다.
public interface MemoRepository {
Memo saveMemo(Memo memo);
}
public class MemoRepositoryImpl implements MemoRepository {
private final Map<Long, Memo> memoList = new HashMap<>();
@Override
public Memo saveMemo(Memo memo) {
Long memoId = memoList.isEmpty() ? 1 : Collections.max(memoList.keySet()) + 1;
memo.setId(memoId);
memoList.put(memoId, memo);
return memo;
}
}
- 실제 DB 와 연결된다면 위와 같은 자료 구조방식은 사용하지 않게 된다는 점 유의하고 봐야한다.
** 해당 부분은 따로 포스팅 하는게 더 좋을 거 같아서 아래 링크를 통해서 CRUD 를 더 자세히 살펴볼 수 있다.
2025.03.20 - [백엔드 부트캠프/TIL] - [내일배움캠프Spring-23일차] JDBC 를 이용한 CRUD 이해하기 [MySql]
[내일배움캠프Spring-23일차] JDBC 를 이용한 CRUD 이해하기 [MySql]
1️⃣ 사전 작업1. Entity 패키지일단 Entity 를 만들어 준다.public class Memo { // filed 를 정의한다 private Long id; private String title; private String contents; } 2. Dto 패키지그리고 ReponseDto 응답 Dto 와@Getter@AllArgsCo
sintory-04.tistory.com
'백엔드 부트캠프 > TIL' 카테고리의 다른 글
[내일배움캠프Spring-24일차] 일정 관리 과제 필수 기능 구현 (2) | 2025.03.21 |
---|---|
[내일배움캠프Spring-23일차] JDBC 를 이용한 CRUD 이해하기 [MySql] (0) | 2025.03.20 |
[내일배움캠프Spring-21일차] Spring 입문 주차 2 (0) | 2025.03.18 |
[내일배움캠프Spring-20일차] Spring 입문 주차 (1) | 2025.03.17 |
[내일배움캠프Spring-19일차] 다형성 알아보기 (0) | 2025.03.14 |