본문 바로가기
IT 프로젝트 공모전(백엔드)

[Spring boot] 회원가입 기능구현 3(Repository, DTO)

by CromArchive 2025. 7. 23.
반응형

Repository 패키지

데이터베이스와 직접 상호작용하는 계층으로, 애플리케이션의 비즈니스 로직(Service)에서 저장·조회·수정·삭제(CRUD) 작업을 추상화하는 부분이다.

리퍼지터리는 interface이고 JpaRepository을 상속받는다.

UserRepository

여기서 사용하는 리포지터리는 UserRepository뿐이므로, 아래처럼 작성해주었다.

package ssu.cromi.teamit.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import ssu.cromi.teamit.domain.User;
import java.util.Optional;

public interface UserRepository extends JpaRepository<User, String> {
    Optional<User> findByEmail(String email);
    Optional<User> findByUid(String uid);
    boolean existsByEmail(String email);
    boolean existsByUid(String uid);
}

Optional은 있어도 없어도 실행하라는 의미이다.

 

메서드 시그니처 설명
Optional<User> findByEmail(String) 이메일(email)이 일치하는 사용자 단건 조회 (없으면 Optional.empty())
Optional<User> findByUid(String) 아이디(uid)가 일치하는 사용자 단건 조회
boolean existsByEmail(String) 이메일 존재 여부 확인 (true=DB에 해당 이메일이 이미 존재)
boolean existsByUid(String) 아이디 존재 여부 확인

 

DTO 패키지

DTO(Data Transfer Object)는 계층 간에 데이터를 주고받기 위해 사용. 주로 컨트롤러와 서비스, 또는 서비스와 리포지토리 사이에서 순수한 데이터만 담아 전달하는 용도이다.

주 역할을 정리하면 아래와 같다.

계층 분리

  • 도메인 엔티티를 외부에 그대로 노출하지 않고, 필요한 필드만 추려서 전송

보안 및 유효성 검증

  • 클라이언트에 노출하면 안 되는 민감한 필드(예: PW, 내부 식별자 등)를 제외
  • @Valid와 함께 사용해 요청(Request) 데이터 검증

API 응답 포맷 표준화

  • 응답할 때 항상 같은 구조로 데이터를 보내도록 보장
  • 버전 관리(v1, v2 API) 시 요청·응답 형태를 유연하게 변경

DTO 계층 구성

DTO 패키지 폴더 구성은 위와 같이 했다.

회원가입 과정에 사용하는 DTO는 SignupRequest, UserReponse, ApiResponse이다.

회원가입 요청에서 SignupRequenst가, 응답에 UserRespons, ApiResponse가 사용된다.

SignupRequest

SignupRequest는 클라이언트에서 전달된 회원가입 요청 데이터를 받기 위한 DTO이다.

소스코드는 아래와 같다.

package ssu.cromi.teamit.DTO.auth;
//회원가입 DTO
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
import lombok.*;
import org.hibernate.validator.constraints.Length;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
public class SignupRequest {
    @JsonProperty("uid")
    @NotBlank(message = "UID 누락됨")
    private String uid;

    @JsonProperty("nickName")
    @NotBlank(message = "닉네임 누락됨")
    private String nickName;

    @JsonProperty("password")
    @ToString.Exclude // pw는 toString, Equals 제외
    @EqualsAndHashCode.Exclude
    @NotBlank(message = "PW 누락됨")
    @Length(min = 8, max=20, message = "최소길이 8, 최대길이 20")// pw 최소, 최대 길이 설정
    private String password;

    @JsonProperty("email")
    @NotBlank(message = "이메일 누락됨")
    @Email(message = "유효한 이메일 형식이 아님")
    private String email;

    @JsonProperty("emailVerified")
    private boolean emailVerified;

    @JsonProperty("birthDay")
    private Integer birthDay;
}
  • @JsonProperty("")는 Jackson이 JSON 키와 Java 필드를 매핑하도록 지정하는 어노테이션이다.
  • 요청에서 JSON값이 “uid” : “Tester123”으로 올 때, uid 필드에 값이 들어가는 형태이다.
  • @Email, @NotBlank, @Length등은 Validation어노테이션으로 제약 사항을 자동으로 검사해준다.
  • 위반 시 400코드와 에러 메시지를 전송한다.
  • 컨트롤러에서 사용할 때는 아래처럼 사용하면 된다.
@PostMapping("/v1/auth/users")
public ApiResponse<UserResponse> signup(@RequestBody @Valid SignupRequest dto) {
    // dto.getUid(), dto.getPassword()
}

UserResponse

UserResponse는 회원가입·로그인 이후 클라이언트에게 반환할 사용자 정보를 담는 응답용 DTO이다.

소스코드는 아래와 같다.

package ssu.cromi.teamit.DTO.common;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;

@Getter
public class UserResponse {
    @JsonProperty("UID")
    private String uid;

    @JsonProperty("email")
    private String email;

    @JsonProperty("nickName")
    private String nickName;

    @JsonProperty("Created_at")
    private String createdAt;

    @JsonProperty("BirthDay")
    private Integer birthday;

    public UserResponse(String uid, String email, String nickName, String createdAt, Integer birthday) {
        this.uid = uid;
        this.email = email;
        this.nickName = nickName;
        this.createdAt = createdAt;
        this.birthday = birthday;
    }
}
  • 생성자를 설정해주었기 때문에 응답은 정해진 순서대로 받고 Setter가 없기 때문에 값은 변경할 수 없다.

ApiResponse

ApiResponse는 모든 API 응답을 일관된 형식으로 감싸기 위한 제네릭 응답 DTO이다.

package ssu.cromi.teamit.DTO.common;

import lombok.*;

@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApiResponse <T>{
    private boolean success;
    private String message;
    private String code;
    private T data;

    /** 성공 응답 생성
     * @param data 실제 반환 데이터
     * @param code 응답코드
     * @param message 응답 메시지
     */
    @Builder(builderMethodName = "ofSuccess")
    public static <T> ApiResponse<T> success(T data, String code, String message){
        return new ApiResponse<>(true, code, message, data);
    }
    /**
     * 성공 응답 생성(메시지 없지)
     * @param data 실제 반환 데이터
     * @param code 응답코드
     */
    public static <T> ApiResponse<T> success(T data, String code){
        return success(data, code,"요청에 성공했습니다.");
    }
    /**
     * 실패 응답 생성
     * @param message 실패 이유 메시지
     * @param code 에러 코드
     */
    public static <T> ApiResponse<T> error(String code, String message){
        return new ApiResponse<>(false, code, message, null);
    }
}

  • @NoArgsConstructor(access = AccessLevel.PROTECTED)
  • Jackson 같은 라이브러리가 역직렬화할 때 사용하는 protected 인자 없는 생성자를 생성
  • @AllArgsConstructor(access = AccessLevel.PRIVATE)
  • 모든 필드를 인자로 받는 생성자는 private 으로 생성되어, 외부에서 직접 new로 객체를 만들 수 없게 제한
  • 이렇게 생성자를 숨기고, 팩토리 메서드Builder를 통해서만 인스턴스를 만들도록 강제
  • @Builder(builderMethodName = "ofSuccess") ApiResponse.<T>ofSuccess()…build() 형태의 빌더를 생성
  • 아래와 같이 사용하면 된다.
ApiResponse<UserResponse> resp = ApiResponse.<UserResponse>ofSuccess()
    .data(userDto)
    .code("USER_CREATED")
    .message("회원가입에 성공했습니다.")
    .build();
  • public static <T> ApiResponse<T> success(T data, String code) {
    return success(data, code, "요청에 성공했습니다.");
    }
    메시지 생략 시 “요청에 성공했습니다”를 기본으로 전송함
    아래와 같이 사용하면 된다.
public static <T> ApiResponse<T> success(T data, String code) { 
	return success(data, code, "요청에 성공했습니다."); 
}
  • public static <T> ApiResponse<T> error(String code, String message) {
    return new ApiResponse<>(false, code, message, null);
    }
    실패 응답 메서드이다.
    아래와 같이 사용하면 된다.
ApiResponse<Void> err = ApiResponse.error("DUPLICATE_UID", "이미 존재하는 UID입니다.");

 

728x90
반응형