티스토리 뷰
안녕하세요! 오늘은 현업에서 많이 사용하는 DTO 클래스에 생성자 이후 자동으로 적용하는 어노테이션을 적어보려고 글을 작성하게 되었습니다. DTO 클래스는 우리가 API 호출을 할 때 엔티티 클래스를 사용하여 우리가 소유하고 있는 데이터베이스의 내용을 은닉하기 위해 사용하는 클래스라고 생각해주시면 됩니다.
예를 들어 데이터베이스에 account_id, username, email 등과 같이 되어 있는 엔티티가 존재하는 경우 이를 그대로 사용하면 사용자의 정보와 account_id 가 그대로 노출되기 때문에 이를 방지하고자 만드는 것입니다. 아래에는 Account class의 예제를 적어보았습니다. 이 클래스에는 이거 말고도 다른 사용자의 개인 정보를 담을 수 있는 컬럼들이 여럿 있겠죠.
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer accountId;
private String username;
private String email;
...
}
그래서 이를 대체하기 위해 아래와 같이 Account 클래스와 형태가 유사한 DTO 클래스를 작성해서 사용합니다.
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AccountDTO {
@JsonIgnore
private Integer id; // 사용은 하지만 클라이언트에서 볼 수 없도록 처리합니다.
private String name; // username은 name으로 치환하였습니다.
private String email; // 일부 property는 그대로 사용합니다.
...
}
여기서 @JsonIgnore 어노테이션의 경우 Jackson에서 제공해주는 것으로 API 통신시에 JSON 형태로 데이터를 Response에 담아 보내주게 되는데, 이러한 경우 Response에 담기긴 하지만 사용자단에서는 볼 수 없도록 처리합니다.
그런데 이러한 경우가 있습니다. 데이터를 조회해온 후 컬럼을 전체 set을 해야하는데 일일히 사용하는 서비스마다 생성자를 호출한 후 이를 빌더 패턴을 사용해서 바인딩을 시켜야하는 경우.
@Service
public class AccountServiceImpl {
@Autowired
private AccountRepository accountRepository;
...
public AccountDTO makeAccountDTO(Account account) {
return AccountDTO.builder()
.id(account.getAccountId())
.name(account.getUsername())
.email(account.getEmail())
... // 그 외에 컬럼
.build();
}
public AccountDTO makeAccountDTO(Integer accountId) {
Account account = accountRepository.findById(accountid);
return AccountDTO.builder()
.id(account.getAccountId())
.name(account.getUsername())
.email(account.getEmail())
... // 그 외에 컬럼
.build();
}
}
이렇게 make 메소드를 만들어서 사용할 수 있지만 컬럼 갯수가 많아지면 그만큼 서비스의 로직도 길어지고 모든 데이터를 빌더로 편하게 매핑하지만 결국은 Entity 클래스의 내용을 getter를 사용하여 담아주어야합니다. 그리고 마찬가지로 데이터를 저장하거나 수정하는 경우도 Entity 클래스에 setter를 사용하여 담아주어야 합니다.
그러면 여기에 유틸성이 가미된 내용으로 추가를 해야 하는 경우는 어떨까요? 예시를 찾아보겠습니다.
@Service
public class AccountServiceImpl {
@Autowired
private AccountRepository accountRepository;
private static final String DEFAULT_EMAIL_POSTFIX = "tistory.com";
...
public AccountDTO makeAccountDTO(Integer accountId) {
Account account = accountRepository.findById(accountid);
return AccountDTO.builder()
.id(account.getAccountId())
.name(account.getUsername())
.emailId(getEmailPostfix(account.getEmail())
.build();
}
public Account saveAccountFromDTO(AccountDTO dto) {
Account account = new Account();
account.setEmail(dto.getEmailId() + "@" + DEFAULT_EMAIL_POSTFIX);
account.setEmailDomain(DEFAULT_EMAIL_POSTFIX);
accountRepository.save(account);
}
public static String getEmailPostfix(String email) {
String[] emailSplited = email.split("@");
if(Objects.isNull(emailSplited)) {
return DEFAULT_EMAIL_POSTFIX;
}
if(emailSplited.length > 1)
return email.split("@")[1];
else
return DEFAULT_EMAIL_POSTFIX;
}
}
위의 코드에서 고정적으로 사용하는 유틸클래스가 있는데 이 부분은 서비스에 있기에는 장황하고 account 엔티티 클래스가 생성될 때마다 매번 고정적으로 사용하는 유틸 메소드를 호출하고 있습니다.
이런 경우 사용하는 것은 바로 PostLoad 입니다. 사용 방법은 Entity 클래스 내에 사용할 수 있도록 맞출 수 있습니다. 리턴은 void입니다.
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer accountId;
private String username;
private String email;
...
// 아래처럼 생성자가 호출되고 나서 자동으로 변환해줍니다.
@PostLoad
public void postConstruct() {
this.email = getEmailPostfix() + "@" + AccountConstants.DEFAULT_EMAIL_POSTFIX;
}
private String getEmailPostfix() {
String[] emailSplited = this.email.split("@");
if(Objects.isNull(emailSplited)) {
return DEFAULT_EMAIL_POSTFIX;
}
if(emailSplited.length > 1)
return email.split("@")[1];
else
return DEFAULT_EMAIL_POSTFIX;
}
}
이렇게 엔티티 클래스에 적용하게 되면 서비스 로직의 간소화 및 번번히 DTO 파싱 및 저장 클래스를 각각의 서비스에서 호출할 필요 없이 자동으로 생성됩니다.
'Server' 카테고리의 다른 글
[Java] Map의 확장 형태인 NavigableMap (0) | 2021.11.27 |
---|---|
[Java] Lists 의 partition 메소드 사용법 (7) | 2021.11.25 |
[Java] JPA 사용시 Truncate 사용방법 (4) | 2021.11.25 |
[Java] QueryDSL 을 사용하여 Multi Data Source 사용하기 (0) | 2021.11.13 |
JMeter 부하 테스트 사용하기 (0) | 2021.11.12 |
Mysql Timestamp 과 Datetime의 차이 (0) | 2021.11.12 |