2026. 04. 09
TO DO LIST
- 3 Layer Architecture 구조 이해하기 Done
- Entity, JPA 개념 잡기 Done
- Repository, Service, Controller 작성하기 Done
- DTO (Request / Response) 설계하기 Done
- User CRUD API 완성 (저장, 조회, 수정, 삭제) Done
- ResponseEntity로 HTTP 상태코드 응답 처리하기 Todo
···
3 Layer Architecture 란?
오늘 실습의 핵심 뼈대. 역할에 따라 계층을 나눠서 코드를 관리하는 구조다. 각 계층이 딱 자기 역할만 하도록 분리하는 게 핵심이다.
| 계층 | 역할 |
|---|---|
| Controller | 요청을 받고 응답을 돌려주는 창구 |
| Service | 비즈니스 로직 처리 (Request → Entity → Response) |
| Repository | DB에 직접 접근하는 유일한 계층 |
Entity가 뭔지 이해하는 데 시간이 걸렸다
처음엔 그냥 클래스 아닌가 싶었는데, JPA를 쓰면 이 클래스가 DB 테이블이랑 1:1로 매핑된다는 걸 배웠다. DB의 행(row) 하나 = Java 객체 하나.
@Getter @Entity @Table(name = "users") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 50, nullable = false) private String name; @Column(unique = true, nullable = false) private String email; private String address; }
@NoArgsConstructor(access = AccessLevel.PROTECTED) — JPA는 내부적으로 기본 생성자가 필요하다. 근데 외부에서 new User()처럼 텅 빈 객체를 만드는 건 막고 싶어서 PROTECTED로 설정한다. JPA는 쓸 수 있되, 개발자가 실수로 쓰는 건 컴파일 타임에 막아주는 안전장치.
SQL 없이 DB를 다루는 방법
JPA는 Java 코드로 DB를 다룰 수 있게 해주는 기술이다. 우리가 Java로 메서드를 호출하면 JPA가 알아서 SQL로 번역해서 DB에 보내준다.
Repository는 JpaRepository를 상속받는 것만으로 기본 CRUD 기능을 다 가져온다. 코드 안에 아무것도 안 써도 된다.
public interface UserRepository extends JpaRepository<User, Long> { // 아무것도 안 써도 save, findById, findAll, delete 다 됨 }
JpaRepository<User, Long> — 첫 번째는 다룰 엔티티, 두 번째는 그 엔티티의 PK 타입. User의 id가 Long이라서 Long을 넣었다.
Entity를 그대로 쓰면 안 되는 이유
처음엔 Entity를 그대로 반환하면 되지 않나 싶었는데, 두 가지 문제가 있다는 걸 배웠다. 첫째로 password 같은 민감한 정보까지 노출될 수 있고, 둘째로 DB 구조가 바뀌면 API 응답도 같이 바뀌어버린다.
| DTO | 용도 | 특징 |
|---|---|---|
| CreateUserRequest | 클라이언트 → 서버 | id 없음, final 없음 (Spring이 채워줌) |
| CreateUserResponse | 서버 → 클라이언트 | id 있음, final 있음 (값 변경 불가) |
Request에 id가 없는 이유 — id는 DB가 자동 생성하는 값이라 클라이언트가 아직 알 수 없다. 저장되고 나서야 id가 생기기 때문에 Response에만 id가 들어간다.
비즈니스 로직이 뭔지 알게 됐다
Service는 데이터를 저장하는 곳이 아니라, 받아서 처리하고 넘겨주는 곳이다. Request를 Entity로 변환 → Repository에 저장 → 결과를 Response로 변환해서 반환하는 흐름을 담당한다.
@Transactional public CreateUserResponse save(CreateUserRequest request) { User user = new User( request.getName(), request.getEmail(), request.getAddress() ); User savedUser = userRepository.save(user); return new CreateUserResponse( savedUser.getId(), savedUser.getName(), savedUser.getEmail(), savedUser.getAddress() ); }
@RestController vs @Controller
옛날 방식은 서버에서 HTML까지 직접 만들어서 돌려줬다. 요즘은 프론트엔드가 화면을 담당하고 서버는 JSON 데이터만 주고받기 때문에 @RestController를 쓴다. @Controller에 JSON 자동 변환이 더해진 것!
@RestController @RequiredArgsConstructor public class UserController { private final UserService userService; @PostMapping("/users") public CreateUserResponse createUser(@RequestBody CreateUserRequest request) { return userService.save(request); } @GetMapping("/users/{userId}") public GetOneUserResponse getOneUser(@PathVariable Long userId) { return userService.getOne(userId); } @PutMapping("/users/{userId}") public UpdateUserResponse update(@PathVariable Long userId, @RequestBody UpdateUserRequest request) { return userService.update(userId, request); } @DeleteMapping("/users/{userId}") public void delete(@PathVariable Long userId) { userService.delete(userId); } }
···
🐼 오늘 학습을 마치며
아직도 많이 어렵지만 어노테이션 하나하나가 왜 붙는지 어떤 역할을 하는지 공부하며 실습해보니 약간은 알 것 같다.
JAVA에서 하던 것처럼(속성, 생성자, 기능 / Main에서 객체 생성) 흐름은 비슷했다.
하지만 Spring은 클라이언트에서 Http 요청을 하면 Layer Architecture 패턴으로 흘러가고 의존성 주입,
그리고 서로 데이터를 주고 받을 때 DTO를 사용하고 또 Entity에서 데이터를 받아
그걸 넘기고 저장하고 이런 흐름인 것 같다. 계속 코딩하다보니 약간의 패턴이 보였다.
아직 많은 컴포넌트가 낯설게 느껴져서 많이 해보며 익숙해져야 할 것 같다.
내일은 ResponseEntity로 HTTP 상태코드까지 응답에 담는 걸 해볼 예정이다.