[SpringBoot + JPA] DTO, Entity의 빌더패턴 적용기

반응형
728x90
반응형

도입

스프링부트 프로젝트에서 JPA를 사용하여 API를 만드는 상황에 놓였다. 나는 API의 파라미터로 받아올 DTO 파일을 생성하였고, JPA Repository로 보낼 Entity 파일을 생성하였다. 

com.api.seohae
   - dto
     UsersDTO.java
      - entity
         Users.java

위 구조로 갔을때 만나게될 상황을 자세히 살펴보자.

 

 

상황분석

Users 테이블에 회원을 등록하는 간단한 POST API를 보자.

@PostMapping("")
public ResponseEntity<?> addUser(@ModelAttribute UsersDto usersDto) {
    Users users = userService.addUser(usersDto);

    return ...;
}

 

파라미터로 UsersDto를 받았다. userService의 addUser 메소드를 살펴보자. 

public Users addUser(UsersDto usersDto) {
    /* 파라미터 셋팅 */
    Users users = new Users();
    users.setUserId(usersDto.getUserId());
    users.setUserPwd(usersDto.getUserPwd());
    users.setUserSex(usersDto.getUserSex());
    users.setUserPhone(usersDto.getUserPhone());

    /* save (insert) */
    users = usersRepository.save(users);

    return users;
}

 

위 코드로 개발한 이후, usersDto 객체의 필드들을 Users 클래스의 필드에 매핑해주는 로직이 들어간다.

 

set메서드 호출보다 더 좋은 방법이 없을까?

참고로 UsersRepository 인터페이스는 아래와 같다.

public interface UsersRepository extends JpaRepository<Users, Long> { }

 

 

빌더패턴의 적용 전, 읽고가기

빌더패턴을 사용함으로써 느낄 수 있는 이점은 아래 포스팅을 참고바란다. 

devfunny.tistory.com/337

 

빌더 패턴의 권장 이유

생성자의 단점 생성자에는 제약이 하나 있는데, 선택적 매개변수가 많을 경우에 대응이 어렵다. 예를들어, 받아오는 매개변수에 따라 계속해서 생성되는 생성자의 코드를 보았을때 매개변수의

devfunny.tistory.com

 

 

빌더패턴 적용기

(1) UsersDto.java

import lombok.Data;

@Data
public class UsersDto {
    private Long idx;
    private String userId;
    private String userPwd;
    private String userSex;
    private String userPhone;
}

 

UsersDto.java 는 빌더패턴 적용 이전, 이후에도 변함이 없다. 아래 entity/Users.java 파일의 전/후를 비교해보자.

 

빌더패턴 적용 이전

(2) Users.java

import lombok.Data;
import javax.persistence.*;

@Entity
@Getter @Setter
@Table(name="USERS")
@SequenceGenerator(
        name="USERS_SEQ_GN", // 시퀀스 제너레이터 이름
        sequenceName="USERS_SEQ", // 시퀀스 이름
        initialValue=1, // 시작값
        allocationSize=1
)
public class Users {
    @Id
    @GeneratedValue(
            strategy= GenerationType.SEQUENCE,
            generator="USERS_SEQ_GN"
    )
    private Long idx;

    private String userId;
    private String userPwd;
    private String userSex;
    private String userPhone;
}

 

단순하게 JPA Entity 파일이다. USERS 테이블과 USERS_SEQ 라는 유저 시퀀스를 선언하고 각 필드를 선언하였다.

 

빌더패턴 적용 이후

(3) CommonBuild.java

  • 빌더패턴 공통화를 위한 인터페이스를 선언하였다. 선언하지 않고 build 메소드를 작성하여도 된다.
/**
 * DTO 빌더패턴 적용 공통 인터페이스
 * @param <T>
 */
public interface CommonBuilder<T> {
    T build();
}

 

(3) Users.java

import com.api.match.common.Buildable;
import com.api.match.dto.UsersDto;
import lombok.Data;
import javax.persistence.*;

@Entity
@Getter @Setter
@Table(name="USERS")
@SequenceGenerator(
        name="USERS_SEQ_GN", // 시퀀스 제너레이터 이름
        sequenceName="USERS_SEQ", // 시퀀스 이름
        initialValue=1, // 시작값
        allocationSize=1
)
public class Users {
    @Id
    @GeneratedValue(
            strategy= GenerationType.SEQUENCE,
            generator="USERS_SEQ_GN"
    )
    private Long idx;

    private String userId;
    private String userPwd;
    private String userSex;
    private String userPhone;

    /* Users 생성자 빌더패턴 적용  */
    private Users(UsersBuilder builder) {
        this.idx = builder.idx;
        this.userId = builder.userId;
        this.userPwd = builder.userPwd;
        this.userSex = builder.userSex;
        this.userPhone = builder.userPhone;
    }

    /* 기본생성자 */
    public Users() {}

    /**
     * Users 클래스의 빌더 클래스
     */
    public static class UsersBuilder implements CommonBuilder<Users> {
        private final Long idx;
        private final String userId;
        private final String userPwd;
        private final String userSex;
        private final String userPhone;

        /* 생성자 */
        public UsersBuilder(UsersDto usersDto) {
            this.idx = usersDto.getIdx();
            this.userId = usersDto.getUserId();
            this.userPwd = usersDto.getUserPwd();
            this.userSex = usersDto.getUserSex();
            this.userPhone = usersDto.getUserPhone();
        }

        /* build 메소드 호출로 Users 객체 리턴 */
        @Override
        public Users build(){
            return new Users(this);
        }
    }
}

 

비교를 위해 2개의 파일을 한번에 적었다. 우선 Users.java 파일 안에 UsersBuilder 라는 클래스를 생성했다. UsersBuilder의 생성자를 UsersDto를 받아오는 메소드로 선언했고, CommonBuild 인터페이스를 상속하여 Users 객체를 리턴해주는 build 메서드를 구현하도록 하였다. 그리고 Users 클래스에도 방금 작업한 UsersBuilder 타입의 객체를 받아오는 생성자를 선언하였다. 

 

 

결과

이렇게 해줌으로써, DTO -> Entity 작업의 코드는 아래와 같이 변경되었다.

 

빌더패턴 적용 이전

public Users addUser(UsersDto usersDto) {
    /* 파라미터 셋팅 */
    Users users = new Users();
    users.setUserId(usersDto.getUserId());
    users.setUserPwd(usersDto.getUserPwd());
    users.setUserSex(usersDto.getUserSex());
    users.setUserPhone(usersDto.getUserPhone());

    /* save (insert) */
    users = usersRepository.save(users);

    return users;
}

 

빌더패턴 적용 이후

public Users addUser(UsersDto usersDto) {
    /* dto -> entity builder */
    Users users = new Users.UsersBuilder(usersDto).build();

    /* save (insert) */
    users = usersRepository.save(users);

    return users;
}

 

 

 

 

 

반응형

Designed by JB FACTORY