목차
요구사항
Validation을 활용해 다양한 예외처리를 적용해 봅니다. → 1주차 Bean Validation 참고!
정해진 예외처리 항목이 있는것이 아닌 프로젝트를 분석하고 예외사항을 지정해 봅니다.
- ✅ Ex) 할일 제목은 10글자 이내, 유저명은 4글자 이내
- ✅ @Pattern을 사용해서 회원 가입 Email 데이터 검증 등
- ✅ 정규표현식을 적용하되, 정규표현식을 어떻게 쓰는지 몰두하지 말 것!
- ✅ 검색해서 나오는 것을 적용하는 것으로 충분!
요구 구현
User (Entity)
더보기
@Getter
@Entity
@Table(name = "user")
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
public User() {
}
public void updateUsername(String username) {
this.username = username;
}
public void updateEmail(String email) {
this.email = email;
}
}
- `@Id`, `@GeneratedValue(strategy = GenerationType.IDENTITY)` : Java의 JPA(Java Persistence API)에서 Entity의 기본 키(Primary Key)를 정의할 때 사용하는 어노테이션입니다.
- `@Id` : 해당 필드가 Entity의 기본 키(Primary key)임을 나타냅니다.
- `@GeneratedValue(strategy = GenerationType.IDENTITY)` : 기본 키의 값을 자동으로 생성하도록 JPA에 지시합니다. (INSERT 할 때 기본 키 값을 따로 지정하지 않아도 DB가 알아서 증가된 값을 넣어줍니다.)
- `@Colmun` : JPA에서 Entity 클래스의 필드를 데이터베이스 테이블의 열(Column)에 매핑할 때 사용됩니다. 생략해도 기본 동작은 하지만, 세부 설정을 하고 싶을 때 사용합니다.
- nullable = false : `NULL` 허용하지 않음
- length = 100 : 문자열 길이 100 (기본 255)
- unique = true : 유일한 값으로 설정
CreateUserRequestDto
더보기
@Getter
@AllArgsConstructor
public class CreateUserRequestDto {
@NotNull
@Size(max = 100)
private final String username;
@NotNull
@Email(message = "이메일 형식이 올바르지 않습니다")
private final String email;
@NotNull
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&#])[A-Za-z\\d@$!%*?&#]{10,}$",
message = "비밀번호는 10자 이상이며, 대소문자, 숫자, 특수문자를 포함해야 합니다."
)
private final String password;
}
- `@NotNull` : null 값을 받지 않는 유효선 검사 어노테이션
- `@Size(max = 100)` : 최대 길이를 100으로 받는 유효선 검사 어노테이션
- `@Pattern(regexp = ...)` : 비밀번호 10자리 이상, 대소문자 필요, 숫자, 특수문자를 포함해야하는 것을 나타내는 정규식 어노테이션
UpdateUserRequestDto
더보기
@Getter
@AllArgsConstructor
public class UpdateUserRequestDto {
@NotNull
@Size(max = 100)
private final String username;
@NotNull
@Email(message = "잘못된 이메일 형식입니다.")
private final String email;
}
- `@NotNull` : null 값을 받지 않는 유효선 검사 어노테이션
- `@Size(max = 100)` : 최대 길이를 100으로 받는 유효선 검사 어노테이션
- `@Email` : 이메일 형식으로 받는 유효성 검사 어노테이션
DeleteUserRequestDto
더보기
@Getter
@AllArgsConstructor
public class DeleteUserRequestDto {
@NotNull
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&#])[A-Za-z\\d@$!%*?&#]{10,}$",
message = "비밀번호는 10자 이상이며, 대소문자, 숫자, 특수문자를 포함해야 합니다."
)
private final String password;
}
- `@NotNull` : null 값을 받지 않는 유효선 검사 어노테이션
- `@Pattern(regexp = ...)` : 비밀번호 10자리 이상, 대소문자 필요, 숫자, 특수문자를 포함해야하는 것을 나타내는 정규식 어노테이션
Schedule (Entity)
더보기
@Entity
@Table(name = "schedule")
@Getter
public class Schedule extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long userId;
@Column(nullable = false)
private String title;
@Column(nullable = false, length = 100)
private String content;
public Schedule() {}
public Schedule(Long userId, String title, String content) {
this.userId = userId;
this.title = title;
this.content = content;
}
public void updateTitle(String title) {
this.title = title;
}
public void updateContent(String content) {
this.content = content;
}
}
CreateScheduleRequestDto
더보기
@Getter
@AllArgsConstructor
public class CreateScheduleRequestDto {
@NotNull
private Long userId;
@Size(min = 5)
@NotNull
private String title;
@Size(max = 100)
@NotNull
private String content;
}
UpdateScheduleRequestDto
더보기
@Getter
@AllArgsConstructor
public class UpdateScheduleRequestDto {
@NotNull
@Size(max = 10)
private String password;
@Size(min = 5)
@NotNull
private String title;
@Max(100)
@NotNull
private String content;
}
LoginRequestDto
더보기
@Getter
@AllArgsConstructor
public class LoginRequestDto {
@NotNull
@Email(message = "이메일 형식이 올바르지 않습니다")
private String email;
@NotNull
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&#])[A-Za-z\\d@$!%*?&#]{10,}$",
message = "비밀번호는 10자 이상이며, 대소문자, 숫자, 특수문자를 포함해야 합니다."
)
private String password;
}
트러블 슈팅
(문제)
- 동일한 Email을 갖는 2명의 유저를 생성하였습니다.
- 해당 이메일(`test@naver.com`)로 로그인 시, `Query did not return a unique result: 2 results were returned`라는 메시지와 함께 오류가 발생합니다.
- 서버 오류에서는 `NonUniqueResultException`이 발생하였습니다.
- `NonUniqueResultException` : 결과가 정확히 하나여야 하는 쿼리에서 여러 개의 결과가 나올 때 발생하는 예외입니다.
(원인)
- 로그인을 진행하는 로직에서 유저 이메일을 검증하고 하나의 유저를 리턴 받을 때, 위 코드와 같이 실행됩니다.
- 하나의 유저만 받아야 하는 상황에서 Email이 동일한 두 유저가 반환되기 때문에 발생하는 오류입니다.
(해결)
- 고유한 이메일을 갖도록 `@Column(unique = true)`를 통해 오류를 해결하였습니다.
회고
유효성 검사 어노테이션(`@NotNull`, `@Email`, `@Size` 등)과 DB 제약 어노테이션(`@Column`)을 같이 사용하는 이유와 언제 사용해야 하는지 궁금했습니다.
유효성 검사 어노테이션은 DTO에서, DB 제약 어노테이션은 Entity에서 하는 것이 관례라고합니다. 그 이유는 다음과 같습니다.
1. 책임 분리(Separation of Concerns)
클래스 | 책임(역할) |
DTO (Data Transfer Object) | 클라이언트의 요청/응답 데이터를 담고 유효성 검사를 수행 |
Entity (도메인 모델) | DB와 매핑되고, 비즈니스 로직을 담는 핵심 도메인 모델 |
- DTO는 주로 외부(프론트엔드 등)에서 넘어온 입력값을 검증하는 데 초점
- Entity는 DB와의 매핑, 비즈니스 규칙 적용에 초점
2. 유효성 검증은 컨트롤러/서비스 단에서 처리해야 사용자에게 피드백이 빠릅니다.
- Entity에 유효성 검사 어노테이션을 넣으면, Entity가 생성될 때만 유효성 검사가 동작합니다.
- 하지만 보통은 사용자의 입력값이 잘못됐을 때, 컨트롤러 진입 전에 에러를 주는 것 이 더 적절합니다.
3. Entity는 외부 변경에 유연해야 합니다.
- 프론트엔드 요구로 입력 필드가 바뀌면, DTO만 수정하면 됩니다.
- 만약 Entity에 직접 유효성 검사를 넣었다면, 도메인 로직과 DB 매핑까지 건드려야 하므로 리스크가 큽니다.
→ DTO는 자주 바뀌고, Entity는 자주 바뀌지 않아야 유지보수가 쉽습니다.
'Project' 카테고리의 다른 글
[Project] Lv_7 스케줄 프로젝트(심화) (0) | 2025.05.23 |
---|---|
[Project] Lv_6 스케줄 프로젝트(심화) (1) | 2025.05.22 |
[Project] Lv_4 스케줄 프로젝트(심화) (0) | 2025.05.21 |
[Project] Lv_3 스케줄 프로젝트(심화) (0) | 2025.05.20 |
[Project] Lv_2 스케줄 프로젝트(심화) (0) | 2025.05.20 |