728x170
해당 글에서는 생성자 패턴 중 Builder() 패턴을 기반으로 상세 속성을 이용하여 심화 내용의 이해를 돕기 위한 목적으로 작성한 글입니다.

💡 해당 글은 이전에 작성한 Builder() 생성자 패턴에 대한 글에 이어지는 내용입니다.
[Java] 생성자 패턴 이해하기 : 점층적 생성자, 자바 빈즈, Builder 패턴)
해당 글에서는 생성자 패턴에 대해서 이해하고, 어떤 패턴으로 생성자를 구성하는 것이 좋을지에 대해서 공유합니다. 1) 생성자 패턴의 종류 1. 점층적 생성자 패턴 (Telescoping Constructor Pattern) 💡
adjh54.tistory.com
1. @Builder(toBuilder = boolean) : default false
💡 Builder의 속성중 ‘toBuilder’는 값을 true로 지정하는 경우 기존에 구성한 빌더를 기반으로 새로운 객체를 재 구성 할 수 있도록 도와주는 속성입니다.
💡 해당 속성의 사용처는 기존의 Builder로 구성한 객체에 값을 재 구성하고자 할 때 사용하면 좋을 것으로 판단됩니다.
package com.adjh.multiflexapi.model;
import com.adjh.multiflexapi.model.common.Pagination;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto extends Pagination {
// 사용자 시퀀스
private int userSq;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
private String userSt = "S";
@Builder(toBuilder = true)
private UserDto(int userSq, String userId, String userPw, String userNm, String userSt) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
}
}
/**
* 객체1을 구성합니다.
*/
UserDto userDto1 = UserDto
.userBuilder()
.userSq(1)
.userNm("Lee")
.userId("adjh54")
.build();
log.debug(userDto1.toString()); // UserDto(userSq=1, userId=adjh54, userPw=null, userNm=Lee, userSt=null)
/**
* 구성한 객체1을 기반으로 객체2를 재구성합니다.
* 객체1의 값은 유지하되 변경하고자 하는 값은 객체2로 변경합니다.
*/
UserDto userDto2 = userDto1
.toBuilder()
.userNm("Jong")
.build();
log.debug(userDto2.toString()); // UserDto(userSq=1, userId=adjh54, userPw=null, userNm=Jong, userSt=null)
1. Collection Type을 toBuilder로 추가방법
💡 Collection Type을 toBuilder로 추가방법
- 사전에 구성된 Dto 객체에 tobuilder 메서드를 통해서 재구성합니다.
- toBuilder 내에서도 Collection Type(List, Map, ...) 타입에 대해서 추가하는 방법에 대해 확인합니다,
💡 toBuilder로 재구성할 Dto입니다.
- 해당 부분에 authUserList라는 List 형태의 변수가 있습니다. 해당 부분을 리스트 형태로 수정할 예정입니다.
package com.adjh.multiflexapi.model;
import com.adjh.multiflexapi.model.common.Pagination;
import lombok.*;
import java.util.List;
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto extends Pagination {
// 사용자 시퀀스
private int userSq;
private long oauthId;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
private String userSt = "S";
private String userEmail;
private List<AuthUserDto> authUserList;
@Builder(toBuilder = true)
public UserDto(int userSq, long oauthId, String userId, String userPw, String userNm, String userSt, String userEmail, List<AuthUserDto> authUserList) {
this.userSq = userSq;
this.oauthId = oauthId;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
this.userEmail = userEmail;
this.authUserList = authUserList;
}
}
💡 재 구성하려는 DTO입니다.
/**
* Auth User 정보를 관리하는 DTO입니다.
*
* @author : jonghoon
* @fileName : AutoUserDto
* @since : 2023/08/05
*/
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AuthUserDto {
private int oauthId;
private int userSq;
private String oauthProvider;
private String userEmail;
private String userNickname;
@Builder(toBuilder = true)
public AuthUserDto(int oauthId, int userSq, String oauthProvider, String userEmail, String userNickname) {
this.oauthId = oauthId;
this.userSq = userSq;
this.oauthProvider = oauthProvider;
this.userEmail = userEmail;
this.userNickname = userNickname;
}
}
💡 테스트 환경의 비즈니스 로직 부분
- 해당 부분에서 파라미터로 받은 userDto 값을 1차로 출력하고 authUserDtoList를 재구성하여서 userDto 내에 tobuilder로 값을 넣어서 Collection Type만 추가하였습니다.
@Override
@Transactional(readOnly = true)
public List<UserDto> selectUserList(UserDto userDto) {
log.debug("userDto 1111111 : " + userDto.toString());
// Builder에 넣을 리스트 선언
List<AuthUserDto> authUserDtoList = new ArrayList<>();
// List 값을 구성
AuthUserDto authUserDto = AuthUserDto
.builder()
.oauthId(1234)
.userSq(userDto.getUserSq())
.oauthProvider("kakao")
.userEmail("adjh54@naver.com")
.userNickname("adjh54")
.build();
// List 내에 요소 추가
authUserDtoList.add(authUserDto);
// toBuilder를 이용하여 List 값을 구성하여 넣습니다.
userDto = UserDto
.builder()
.authUserList(authUserDtoList)
.build();
log.debug("userDto 2222222 : " + userDto.toString());
UserMapper um = sqlSession.getMapper(UserMapper.class);
return um.selectUserList(userDto);
}
💡 결과확인
- Dto 내에 List 값이 추가되었습니다.

2. @Builder(builderMethodName = String)
💡 Builder의 속성중 ‘builderMethodName’은 기본 메서드 명으로 Builder()로 사용이 되는데 이를 다른 이름으로 네이밍을 할 수 있도록 제공하는 속성입니다.
💡 해당 속성의 사용처는 상속 관계에 있는 경우 부모의 Builder와 자식의 Builder 간의 이름이 동일하여 충돌이 발생할 수 있으므로 이에 대해 각각 이름을 지정해주려 할 때 사용하면 좋을 것으로 판단됩니다.
💡 Builder()를 사용하는 도중에 error: builder() in UserDto cannot hide builder() in Pagination 에러를 마주쳤습니다.

💡 이는 부모의 Builder() 메서드와 자식의 Builder() 메서드가 동일한 이름으로 구성이 되어서 발생한 에러였습니다. 이를 위해서 해당 속성을 적용합니다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto extends Pagination {
// 사용자 시퀀스
private int userSq;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
private String userSt;
@Builder(builderMethodName = "userBuilder")
UserDto(int userSq, String userId, String userPw, String userNm, String userSt) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
}
}
/**
* [공통] 페이지 관리
*
* @author : lee
* @fileName : Pagination
* @since : 2022/12/28
*/
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Pagination {
private int page;
private long perPage;
private String sortColumn;
private Boolean sortAscending;
@Builder(builderMethodName = "paginationBuilder")
Pagination(int page, long perPage, String sortColumn, Boolean sortAscending) {
this.page = page;
this.perPage = perPage;
this.sortColumn = sortColumn;
this.sortAscending = sortAscending;
}
}
3. @Singular(String)
💡 Builder와 관련된 @Singular라는 어노테이션은 @Builder() 내에서 컬렉션 객체(List, Set, Map)에 대해서 값을 전체로 넘기지 않고 하나하나씩 넘길 수 있도록 도와줍니다.
💡해당 속성의 사용처는 콜렉션 객체 내의 조작이 있을 경우 변형을 하여서 Builder로 구성을 하는 경우에 도움이 될 것 같습니다.
💡 DTO 내에서 @Singular("userMapItem")와 같은 형태로 컬렉션 객체의 단수로 Alias를 주어서 각각의 빌더를 구성하였습니다.
package com.adjh.multiflexapi.model;
import lombok.*;
import java.util.List;
import java.util.Map;
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserDto {
// 사용자 시퀀스
private int userSq;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
private String userSt;
// TEST - Hashmap
private Map<String, Object> userMap;
// TEST - ArrayList
private List<UserDto> userList;
@Builder(builderMethodName = "userBuilder", toBuilder = true)
private UserDto(int userSq, String userId, String userPw, String userNm, String userSt,
@Singular("userMapItem") Map<String, Object> userMap,
@Singular("userListItem") List<UserDto> userList
) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
this.userMap = userMap;
this.userList = userList;
}
}
💡 구성한 Builder를 통해서 최종적인 호출을 수행합니다.
/**
* 객체1을 구성합니다.
*/
UserDto userDto1 = UserDto
.userBuilder()
.userSq(1)
.userNm("Lee")
.userId("adjh54")
.build();
/**
* 구성한 객체1을 기반으로 객체2를 재구성합니다.
* 객체1의 값은 유지하되 변경하고자 하는 값은 객체2로 변경합니다.
*/
UserDto userDto2 = userDto1
.toBuilder()
.userNm("Jong")
.build();
UserDto userDto3 = UserDto
.userBuilder()
.userSq(1)
.userNm("Lee")
.userId("adjh54")
.userMapItem("userItem1", "Item1")
.userMapItem("userItem2", "Item2")
.userMapItem("userItem3", "Item3")
.userMapItem("userItem4", "Item4")
.userListItem(userDto1)
.userListItem(userDto2)
.userListItem(userDtoThree)
.build();
💡 Collection 객체에 대해서 각각 지정한 값이 출력됨을 확인하였습니다.
UserDto(userSq = 1, userId = adjh54, userPw = null, userNm = Lee, userSt = null, userMap = {
userItem1 = Item1,
userItem2 = Item2,
userItem3 = Item3,
userItem4 = Item4
}, userList = [
UserDto(userSq = 1, userId = adjh54, userPw = null, userNm = Lee, userSt = null, userMap = {}, userList = []),
UserDto(userSq = 1, userId = adjh54, userPw = null, userNm = Jong, userSt = null, userMap = {}, userList = []),
UserDto(userSq = 0, userId = null, userPw = null, userNm = null, userSt = null, userMap = {}, userList = [])
]);
[ 더 알아보기 ]
💡 Java 9 이상 버전의 경우 각각을 구성할 필요없이 Map.of(), Array.of() 함수를 통해서 한 번에 구성하여 콜렉션 객체를 구성하면 좀 더 간단한 구조로 생성이 가능합니다.
[참고] Lombok 공식사이트
@Builder
projectlombok.org
4. @Builder.Default
💡 Builder의 속성 중 ‘Default’는 변수의 앞에 선언이 되며 빌더가 구성이 될 때 초기값을 지정해 줄 수 있도록 도와주는 속성입니다.
💡 아래와 같이 구성을 하였을 경우 ‘userSt’ 컬럼에 “S”라고 초기화를 했습니다. 초기화를 하더라도 Builder로 호출 시 값이 적용되지 않는 점이 있습니다.
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class UserDto {
// 사용자 시퀀스
private int userSq;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
private String userSt = "S";
// TEST - Hashmap
private Map<String, Object> userMap;
// TEST - ArrayList
private List<UserDto> userList;
@Builder(builderMethodName = "userBuilder", toBuilder = true)
private UserDto(int userSq, String userId, String userPw, String userNm, String userSt,
@Singular("userMapItem") Map<String, Object> userMap,
@Singular("userListItem") List<UserDto> userList
) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
this.userMap = userMap;
this.userList = userList;
}
}
💡 [결과값] useSt를 “S” 값으로 초기화를 하였음에도 userSt 값은 null로 출력이 되었습니다.
UserDto(userSq=0, userId=null, userPw=null, userNm=null, userSt=null, userMap={}, userList=[])
💡 그래서 Builder의 초기값을 지정하기 위해서는 Builder.default를 사용해야 합니다.
단, 해당 값은 @NoArgsConstructor, @RequiredArgsConstructor 인 경우에 에러를 발생합니다. 그러므로@AllArgsConstructor 인 상태일 때만 이를 사용합니다.
💡 해당 속성의 사용처는 Builder로 객체를 구성할 때 초기 값을 지정하는 경우에 도움이 될 것 같습니다.
@Getter
@ToString
@AllArgsConstructor
public class UserDto {
// 사용자 시퀀스
private int userSq;
// 사용자 아이디
private String userId;
// 사용자 패스워드
private String userPw;
// 사용자 이름
private String userNm;
// 사용자 상태
@Builder.Default
private String userSt = "S";
// TEST - Hashmap
private Map<String, Object> userMap;
// TEST - ArrayList
private List<UserDto> userList;
}
💡 [참고] 해당 글은 다음글에서 이어집니다.
[Java/Library] Lombok 이해하고 적용하기 -2 : 심화 및 적용
해당 글에서는 이전에 작성한 Lombok 환경 설정 및 이해 글에 이어서 Lombok을 활용하여 심화 활용 기능에 대해 확인합니다. 💡 Lombok 설정 및 기초에 대해서 이해하고 싶은 경우 하단의 이전에 작성
adjh54.tistory.com
오늘도 감사합니다. 😀
그리드형
'Java > 아키텍처 & 디자인 패턴' 카테고리의 다른 글
[Java] RESTful API 설계 방법 -2 : 구성하기 (0) | 2023.03.26 |
---|---|
[Java] RESTful API 설계 방법 -1 : 이해하기 (0) | 2023.03.22 |
[Java] 계층화된 아키텍처(Layered Architecture) : N Tier Architecture (0) | 2023.01.25 |
[Java] 프로그래밍 패러다임 이해하기 (0) | 2023.01.07 |
[Java] 생성자 패턴 이해하기 : 점층적 생성자, 자바 빈즈, Builder 패턴) (0) | 2022.11.12 |