웹페이지의 동작방식으로 크게는 유저(클라이언트) <-> 프론트 <-> 백엔드(서버) <-> 데이터베이스로 동작이 된다. 이때 서버에서는 데이터베이스에서 데이터를 가져와 프론트로 값을 전송해 유저가 볼 수 있도록 동작하게 되는데 이때 값을 가져오는 방식 중 대표적인 것이 DTO클래스이다.
프론트에서 값을 입력받아 백엔드로 보낼때나 백엔드에서 프론트로 값을 보내는 과정 중 불필요하게 객체의 정보가 변경이 될 시 불변성을 보장할 수 없게 되어 다른 값이 저장되거나 오류가 발생할 수 있다. 따라서 이를 위해 DTO클래스를 통해 래핑하여 전달하게 된다.
불변성을 수행 과정
1. 모든 필드에 final을 사용하여 명시적으로 정의
2. 필드 값을 모두 포함한 생성자
3. 각 필드에 대한 getter 접근 메소드
4. 모든 필드가 일치할때 동일한 클래스의 객체에 대해 true를 반환하는 equals 메소드
5. 모든 필드가 일치할때 동일한 값을 반환하는 hashCode 메소드
6. 로깅 출력을 제공하기 위한 toString 재정의
일반적은 DTO 클래스를 보게 되면
public class Student{
private final String name; // 학생 이름
private final int number; // 학급 번호
// 생성자
public Student(String name, int number){
this.name = name;
this.number = number;
}
// getter
public String getName(){
return name;
}
// 해시코드
public int hashCode(){
return Objects.hash(name,address);
}
// 객체 비교
public boolean equals(Object obj){
if(this == obj){
return true;
}else if(!(obj instanceof Student)){
return false;
}else{
Student stu = (Student) obj;
return Object.equals(name, stu.name) && Object.equals(number,stu.number);
}
}
// toString
public String toString(){
return "Student [name="+name+",number="+number+"]";
}
}
이런식으로 사용하게 되면 2개의 필드만 작성했을에도 불구하고 코드의 양의 너무 많아져서 이해하기가 어려울 수 있으며 클래스의 목적이 애매함(너무 많은 메서드를 사용하고 있기 때문에 클래스의 목적이 정보전달만을 하고 있는건지 알기 어려움)
이처럼 단점들을 보완하기 위해 Lombok을 사용하여 작성할 수 있다.
@EqualsAndHashCode
@ToString
@AllArgsConstructor
@Getter
public class Student{
private final String name; // 학생 이름
private final int number; // 학급 번호
}
하지만 이것 또한 라이브러리만을 사용하여 boilerplate code를 줄일 수 있지만 클래스만 봤을때는 아직까지 어떤 목적을 가진 클래스인지 확인할 수 없기 때문에 클래스의 목적이 애매하다.
따라서 JAVA 14이후로는 이러한 반복적인 클래스를 record 키워드를 사용하여 클래스의 목적을 구분시키고 있다.
public record Student(String name, int number){}
record를 선언하는 방법은 위와 같다.
1. class 키워드 대신에 record를 사용하여 데이터를 전송하기 위해 사용하는 파일이라는 것을 지정
2. 생성자에 매개변수를 넣는 것처럼 사용할 필드 값들을 클래스명 옆에 선언해준다.
이렇게만 해주면 자동으로 필드값들은 private final키워드로 취급되고 기본 생성자, getter, toString, hashCode, equals를 생성해준다.
public static void main(String[] args) {
Student stu = new Student("학생1", 1);
}
public static void main(String[] args) {
Student stu = new Student("학생1", 1);
System.out.println(stu.name()); // 학생1
System.out.println(stu.number()); // 1
}
public static void main(String[] args) {
Student stu = new Student("학생1", 1);
Student stu2 = new Student("학생1", 1);
Student stu3 = new Student("학생2", 2);
System.out.println(stu.equals(stu2)); // true
System.out.println(Objects.equals(stu, stu2)); // true
System.out.println(stu.equals(stu3)); // false
}
public static void main(String[] args) {
Student stu = new Student("학생1", 1);
Student stu2 = new Student("학생1", 1);
Student stu3 = new Student("학생2", 2);
System.out.println(stu.hashCode()); // (1)
System.out.println(stu2.hashCode()); // (1) hashCode 값과 일치
System.out.println(stu3.hashCode()); // (1) hashCode 값과 불일치
}
public static void main(String[] args) {
Student stu = new Student("학생1", 1);
System.out.println(stu); // Student[name=학생, number=1]
System.out.println(stu.toString()); // Student[name=학생, number=1]
}