๐ผ DTO(: Data Transfer Object)
public class AccountResponseDto {
private final Long id;
private final String username;
private final String nickname;
}
DTO๋ ๊ณ์ธต๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ ํด๋์ค์ด๋ค. ๋ฐ๋ผ์ DTO์ ๋ฐ์ดํฐ๋ ๋ณํ๋ฉด ์๋๋ค๋ ํน์ง์ ๊ฐ๊ณ ์๋ค.
๋ฐ๋ผ์ ๋ณดํต์ DTO ํด๋์ค ํ๋๋ final ์์ฑ์ ๊ฐ๊ฑฐ๋ setter๋ฅผ ์ ์ธํ์ง ์๋๋ค. ๋ํ DTO ํด๋์ค๋ ๋ณ๋์ ๋ฉ์๋๋ฅผ ํฌํจํ๊ณ ์์ง ์๊ณ ๋จ์ํ ๋ฐ์ดํฐ๋ง์ ํฌํจํ๊ณ ์๋ค.
๐ผ ๋ ์ฝ๋(record)๋?
public record AccountResponseDto(
Long id,
String username,
String nickname
) { }
Record๋ ๋ฐ์ดํฐ๋ง์ ํฌํจํ๋ ๋ถ๋ณ(immutable)ํ ๊ฐ์ฒด๋ฅผ ๊ฐ๋จํ๊ฒ ์ ์ํ ์ ์๊ฒ ๋์์ฃผ๋ Java 14๋ถํฐ ๋์ ๋ ๋ฐ์ดํฐ ํด๋์ค์ด๋ค.
๊ธฐ์กด Java์์ DTO๋ฅผ ์ ์ํ๊ธฐ ์ํด์๋ ์์ฑ์, getter, equals(), hashcode(), toString() ๋ฑ ๋ค์ํ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ(์๋ ์์ฑ ์ฝ๋)๊ฐ ํ์ํ์ผ๋, Record๋ ์ด๋ฐ ๋ถํธํจ์ ํด์ํ๊ณ ๊ฐ๊ฒฐํ ๋ฌธ๋ฒ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํํํ ์ ์๋๋ก ๋์์ค๋ค.
๐ซง ๋ ์ฝ๋ ํน์ง
1. ๋ถ๋ณ์ฑ
Record๋ ๋ชจ๋ ํ๋๋ฅผ final๋ก ์ ์ธํด ํ๋ฒ ์์ฑ๋๋ฉด ๊ทธ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ค.
์๋์ ๊ฐ์ด ํ๋์ ๋ฐ๋ก final์ ์ ์ธํ์ง ์์๋ ํ๋ ๊ฐ ๋ณ๊ฒฝ์ ์ปดํ์ผ ์ค๋ฅ๊ฐ ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
2. getter ๊ธฐ๋ณธ ์ ๊ณต
๊ธฐ๋ณธ์ ์ผ๋ก getter๋ฅผ ์ ๊ณตํ๋ฉฐ, getX()๊ฐ ์๋ x()๋ก ๋ค์ด๋ฐ ์ฒ๋ฆฌ๋๋ค.
// ๋ ์ฝ๋
public record SignupRequestDto(String username, String password, String nickname) { }
// ๋ฉ์ธํจ์
public class Main {
public static void main(String[] args) {
SignupRequestDto request = new SignupRequestDto("username@gmail.com", "password1!", "nickname");
String username = request.username();
}
}
2. ํ์ค ๋ฉ์๋ ์๋ ์ ๊ณต
equals, hashcode, toString ๋ฉ์๋๊ฐ ์๋์ผ๋ก ์ ๊ณต๋๋ฉฐ, ์๋ก ๋ค๋ฅธ record๊ฐ ํ๋ ๊ฐ์ด ๊ฐ์ผ๋ฉด ๊ฐ์ ๊ฐ์ฒด๋ก ์ธ์ํ๋ค.
๋จ, == ๊ธฐํธ๋ ์ ์ฉ๋์ง ์๋๋ค.
public void test() {
SignupRequestDto requestDto1 = new SignupRequestDto("username@gmail.com", "password1!", "nickname");
SignupRequestDto requestDto2 = new SignupRequestDto("username@gmail.com", "password1!", "nickname");
System.out.println(requestDto1 == requestDto2); // false
System.out.println(requestDto1.equals(requestDto2)); // true
System.out.println(requestDto1.hashCode() == requestDto2.hashCode()); // true
}
3. annotation ์ฌ์ฉ ๊ฐ๋ฅ
Record๋ Bean Validation์ ์ ์ฉ์ํฌ ์ ์๋ค.
public record SignupRequestDto(
@NotNull @Email String username,
@NotNull @Pattern(regexp = PASSWORD_REGEXP) String password,
@NotNull @Pattern(regexp = NICKNAME_REGEXP) String nickname
) { }
4. Compact Construct
์์ฑ์์ ์กฐ๊ฑด์์ ์ฝ๊ฒ ์์ฑํ ์ ์๋ ๋ฌธ๋ฒ์ ์ ๊ณตํ๋ค.
public record SignupRequestDto(String username, String password, String nickname) {
public SignupRequestDto {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
Objects.requireNonNull(nickname);
}
}
3. ์์ ๋ถ๊ฐ๋ฅ
Record๋ ๋ค๋ฅธ ํด๋์ค๋ฅผ ์์๋ฐ์ ์ ์์ผ๋ฉฐ, ์์๋๋๋ก ํ ์ ์๋ค.
๐ค ์์์ด ๋ถ๊ฐ๋ฅํด ์ป์ ์ ์๋ ์ด์ ์ ์ด๋ค ๊ฒ์ด ์์๊น?
- ๋ถ๋ณ์ฑ ๋ณด์ฅ
- ์์์ ๊ธ์งํจ์ผ๋ก์จ ๊ฐ์ฒด์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์๋ธํด๋์ค๋ฅผ ๋ฐฉ์งํด ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ๊ณผ ์์ ์ฑ์ ๋์ธ๋ค.
- ๊ฐ๊ฒฐ์ฑ๊ณผ ๋ช
๋ฃ์ฑ
- ์์์ ๊ธ์งํ๋ฉด ํด๋์ค ๊ณ์ธต ๊ตฌ์กฐ๊ฐ ๋จ์ํด์ง๊ณ , ์ดํดํ๊ธฐ ์ฌ์ ์ ์ง๋ณด์๊ฐ ์ฝ๋ค.
- ์ถ์ํ์ ๋ช
ํ์ฑ
- record๋ ๋ช ํํ๊ฒ ๋ฐ์ดํฐ ํ๋(data holder) ์ญํ ์ ํ๋ค. ์์์ ํ์ฉํ์ง ์์์ผ๋ก์จ ์ญํ ์ด ๋ช ํํด์ง๊ณ , ์๋ชป๋ ์ฌ์ฉ์ ๋ฐฉ์งํ ์ ์๋ค.
- ์ฑ๋ฅ ํฅ์
- ์์์ ํ์ฉํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐํ์์ ํด๋์ค ๊ณ์ธต ๊ตฌ์กฐ๋ฅผ ํ์ํ ํ์๊ฐ ์์ด ์ฑ๋ฅ์ด ํฅ์๋ ์ ์๋ค. ๋ํ, ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๋ ์ค์ด๋ค ์ ์๋ค.
4. Lombok ์ฌ์ฉ ๊ฐ๋ฅ
Record์ Lombok ์ ์ฉ์ด ๊ฐ๋ฅํ๋ค.
ํ์ง๋ง @Builder๋ฅผ ์ ์ธํ ๋๋จธ์ง ์ด๋ ธํ ์ด์ ์ ๋๋ถ๋ถ Record์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณต๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ๋ ์ผ์ด ์ ์๋ค.
(@Setter๊ฐ์ด ํ๋์ ์ํฅ์ ๋ผ์น๋ ์ด๋ ธํ ์ด์ ์ฌ์ฉ์ ์ง์)
// ๋ ์ฝ๋
@Builder
public record LoginRequestDto(String username, String password) {}
// ํ
์คํธ ์ฝ๋
public class Test {
public void test() {
LoginRequestDto dto = LoginRequestDto.builder()
.username("username@gmail.com")
.password("password1!")
.build();
}
}
๐ซง DTO๋ฅผ ๋ ์ฝ๋ ํ์ ์ผ๋ก ์ฌ์ฉํ ์ด์
1. ๊ฐ๊ฒฐ์ฑ
DTO ๊ฐ์ ๋ฐ์ดํฐ ์ ์ฉ ํด๋์ค๋ฅผ ์ ์ํ ๋ ํ์ํ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๋ฅผ ์ค์ผ ์ ์๋ค.
๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋: ํน์ ํ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ๋ฐ๋ณต์ ์ผ๋ก ์์ฑํด์ผ ํ๋ ์ฝ๋ ๋ธ๋ก
2. ๋ช ํํ ์๋
Record๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ํด๋น ํด๋์ค๊ฐ ๋ฐ์ดํฐ ์ ์ฉ์์ ๋ช ํํ ๋ํ๋ผ ์ ์๋ค.
3. ๋ถ๋ณ์ฑ
Record๋ ๋ฐ์ดํฐ์ ๋ถ๋ณ์ฑ์ ๋ณด์ฅํด ๋ฒ๊ทธ ๋ฐ์์ ์ต์ํํ๊ณ ์ฝ๋์ ์์ ์ฑ์ ํฅ์์ํจ๋ค.
๐ Class vs Record ์ฝ๋ ๋น๊ต
๊ธฐ์กด: class๋ก Reponse DTO ์์ฑ
record์ ๋ช ํํ ๋น๊ต๋ฅผ ์ํด ์ด๋ ธํ ์ด์ ์ฌ์ฉ X
public class AccountResponseDto {
private final Long id;
private final String username;
private final String nickname;
@Builder
public AccountResponseDto(Long id, String username, String nickname) {
this.id = id;
this.username = username;
this.nickname = nickname;
}
public static AccountResponseDto from(Account account) {
return AccountResponseDto.builder()
.id(account.getId())
.username(account.getUsername())
.nickname(account.getNickname())
.build();
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getNickname() {
return nickname;
}
@Override
public boolean equals(Object o) {
// ...
}
@Override
public int hashCode() {
// ...
}
@Override
public String toString() {
// ...
}
}
์์ : record๋ก Reponse DTO ์์ฑ
์ด๋ ธํ ์ด์ ์์ด getter, equals(), hashCode(), toString() ๋ฉ์๋๊ฐ ์๋ ์์ฑ๋๋ค.
public record AccountResponseDto(
Long id,
String username,
String nickname
) {
public static AccountResponseDto from(Account account) {
return new AccountResponseDto(account.getId(), account.getUsername(),
account.getNickname());
}
}
๊ธฐ์กด: class๋ก Request DTO ์์ฑ
public class SignupRequestDto {
@NotNull(message = "์ด๋ฉ์ผ์ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.")
@Email(message = "์ ํจํ์ง ์์ ์ด๋ฉ์ผ ํ์์
๋๋ค.")
private String username;
@NotNull(message = "๋น๋ฐ๋ฒํธ๋ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.")
@Pattern(regexp = PASSWORD_REGEXP, message = "๋น๋ฐ๋ฒํธ๋ ์๋ฌธ, ์ซ์, ํน์๋ฌธ์ ํฌํจ 10์ ์ด์ 25์ ์ดํ์ฌ์ผ ํฉ๋๋ค.")
private String password;
@NotNull(message = "๋๋ค์์ ํ์ ์
๋ ฅ ํญ๋ชฉ์
๋๋ค.")
@Pattern(regexp = NICKNAME_REGEXP, message = "๋๋ค์์ 2์ ์ด์ 10์ ์ดํ์ฌ์ผ ํฉ๋๋ค.")
private String nickname;
public SignupRequestDto() {}
@Builder
public SignupRequestDto(String username, String password, String nickname) {
this.username = username;
this.password = password;
this.nickname = nickname;
}
public Account toEntity(PasswordEncoder passwordEncoder) {
return Account.builder()
.username(username)
.password(passwordEncoder.encode(password))
.nickname(nickname)
.build();
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getNickname() {
return nickname;
}
@Override
public boolean equals(Object o) {
// ...
}
@Override
public int hashCode() {
// ...
}
@Override
public String toString() {
// ...
}
}
์์ : record๋ก Request DTO ์์ฑ
public record SignupRequestDto(
@NotNull @Email String username,
@NotNull @Pattern(regexp = PASSWORD_REGEXP) String password,
@NotNull @Pattern(regexp = NICKNAME_REGEXP) String nickname
) {
public Account toEntity(PasswordEncoder passwordEncoder) {
return Account.builder()
.username(username)
.password(passwordEncoder.encode(password))
.nickname(nickname)
.build();
}
}
๐ผ ์ ๋ฆฌ
DTO๋ ์๋ก ๋ค๋ฅธ ๊ณ์ธต๊ฐ์ ๋ฐ์ดํฐ ๊ตํ์ ์ํด ์ฌ์ฉ๋๋ฉฐ, ๋จ์ํ ์ ๋ฌํ ๋ฐ์ดํฐ๊ฐ๋ง์ ํฌํจํ๋ ๊ฒ์ด ์ข๋ค. DTO์๋ ํน์ ํ ๋ก์ง์ด ํฌํจ๋ ๋ฉ์๋๋ฅผ ๊ฐ์ง ์๋ ๊ฒ์ด ์ข์ผ๋ฉฐ, ๋ฐ์ดํฐ๊ฐ ๋์ค์ ๋ณํ์ง ์๋๋ก ์ค์ ํด์ค ํ์๊ฐ ์๋ค.
Recod๋ ์ผ๋ฐ ํด๋์ค์ฒ๋ผ ์ฌ์ฉํ ์ ์์ง๋ง, ํ๋์ final์ ์๋์ผ๋ก ์ถ๊ฐํด์ฃผ๊ณ getter์ equals, hashCode, toString ๋ฉ์๋๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํด์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฐ๋ฐ์์๊ฒ ํธ๋ฆฌํ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ ๊ณตํด์ค๋ค.
๋ฐ๋ผ์ ์จ์ ํ ๋ฐ์ดํฐ ๋ถ๋ณ์ฑ์ ์งํค๋ DTO๋ฅผ Record๋ก ๊ตฌํํ๊ฒ ๋๋ฉด, DTO๊ฐ ๊ฐ์ ธ์ผ ํ ํน์ง๋ค์ ๊ฐํธํ๊ฒ ๊ตฌํํ ์ ์๋ค.
'Back-end' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Postman ํ๊ฒฝ๋ณ์ ์๋ ์ธํ (ํ ํฐ๊ฐ ์๋ ์ธํ ) (0) | 2024.05.06 |
---|---|
[Spring Security] ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ ๊ตฌ์ฑ ์์ ์ดํด๋ณด๊ธฐ (0) | 2024.05.02 |
[Spring] Meta Annotation์ด๋? (@Target, @Retention ๋ฑ) (0) | 2024.04.29 |
[JPA] @SQLDelete์ ์์์ฑ ์ปจํ ์คํธ (0) | 2024.04.22 |
[JPA] CascadeType.REMOVE vs orphanRemoval = true (0) | 2024.04.21 |