“DTO는 데이터를 담는 틀, Repository는 DB와의 통신 창구다.”
이 문서에서는 Controller → Service → Repository → DB까지의
전체 데이터 흐름을 예시와 함께 단계별로 살펴봅니다.
회원 가입 및 회원 목록 조회 기능을 구현한다고 가정합니다.
회원 정보를 요청받아 DB에 저장하고,
저장된 회원 목록을 조회할 수 있습니다.
| 컬럼명 | 타입 | 설명 |
|---|---|---|
user_id | BIGINT (PK, AUTO_INCREMENT) | 회원의 고유 ID |
username | VARCHAR(50) | 사용자 이름 |
email | VARCHAR(100) | 이메일 주소 |
role | VARCHAR(20) | 사용자 권한 (ADMIN, MEMBER, GUEST) |
created_at | DATETIME | 가입일시 |
CREATE TABLE users (
user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
실제 DB 테이블의 한 행(Row)을 Java 객체로 표현한 클래스입니다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String username;
private String email;
@Enumerated(EnumType.STRING)
private UserRole role; // enum과 매핑
private LocalDateTime createdAt = LocalDateTime.now();
protected User() {} // JPA 기본 생성자
public User(String username, String email, UserRole role) {
this.username = username;
this.email = email;
this.role = role;
}
// getter/setter 생략
}
enum은 "이 필드는 가능한 값이 정해져 있다"는 제약을 걸어줍니다.
public enum UserRole {
ADMIN,
MEMBER,
GUEST
}
즉, User.role은 ADMIN / MEMBER / GUEST 중 하나만 가능하게 됩니다.
DB Entity를 그대로 외부로 노출하지 않기 위해,
Controller ↔ Service 사이에서 데이터를 옮기는 그릇 역할을 합니다.
public class UserDto {
private String username;
private String email;
private String role;
public UserDto() {}
public UserDto(String username, String email, String role) {
this.username = username;
this.email = email;
this.role = role;
}
// getter/setter 생략
}
Repository는 데이터베이스와 직접 통신하는 창구입니다.
JPA가 내부적으로 SQL을 자동 생성해줍니다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
JpaRepository는 이미 save, findAll, deleteById 등의
CRUD 메서드를 제공하므로 따로 SQL을 작성할 필요가 없습니다.
Service는 비즈니스 규칙을 수행하는 중간 계층입니다.
DTO ↔ Entity 변환, 트랜잭션 관리 등을 담당합니다.
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
// 회원 등록
public UserDto registerUser(UserDto dto) {
UserRole role = UserRole.valueOf(dto.getRole()); // 문자열 → enum
User user = new User(dto.getUsername(), dto.getEmail(), role);
User saved = userRepository.save(user);
return new UserDto(
saved.getUsername(),
saved.getEmail(),
saved.getRole().name()
);
}
// 전체 회원 조회
public List<UserDto> getAllUsers() {
return userRepository.findAll().stream()
.map(u -> new UserDto(
u.getUsername(),
u.getEmail(),
u.getRole().name()
))
.toList();
}
}
사용자의 HTTP 요청을 받아 Service에 전달하고,
결과를 JSON으로 응답합니다.
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// 회원 등록
@PostMapping
public ResponseEntity<UserDto> register(@RequestBody UserDto dto) {
UserDto saved = userService.registerUser(dto);
return ResponseEntity.ok(saved);
}
// 회원 목록 조회
@GetMapping
public ResponseEntity<List<UserDto>> list() {
return ResponseEntity.ok(userService.getAllUsers());
}
}
[클라이언트(JSON 요청)]
↓
@Controller (UserController)
↓
@Service (UserService)
↓
@Repository (UserRepository)
↓
[DB(users 테이블)]
💬 그리고 반대로 DB에서 데이터를 읽어올 때는
역순으로 Controller까지 올라와서 JSON 응답으로 반환됩니다.
| 계층 | 역할 | 예시 클래스 | 다루는 데이터 |
|---|---|---|---|
| Controller | 요청/응답 처리 | UserController | UserDto |
| Service | 비즈니스 로직 수행 | UserService | User ↔ UserDto |
| Repository | DB 접근 | UserRepository | User (Entity) |
| DB | 실제 데이터 저장소 | users 테이블 | SQL 데이터(Row) |
| 개념 | 핵심 역할 | 비유 |
|---|---|---|
| DTO | 데이터를 담는 틀 | 도장의 형틀 |
| Entity | DB 테이블과 매핑되는 실체 | 도장 찍힌 문서 |
| Repository | DB 접근 창구 | 서류를 보관·꺼내주는 창고직원 |
| Service | 비즈니스 로직 담당 | 도장을 찍는 사람 |
| Enum | 값의 범위 제한 | 도장에 사용할 색상 종류 제한 |
DTO는 데이터를 옮기는 그릇.Entity는 실제 DB 테이블과 매핑되는 객체.Repository는 DB에 접근하는 창구.Service는 비즈니스 로직을 수행하고, DTO ↔ Entity를 변환.Enum은 특정 필드 값의 범위를 제한해 안정성을 높인다.