[REST API 및 프로젝트 소개]_05_이벤트 도메인 구현
Event 생성 API 구현: Event 도메인 구현
public class Event {
private String name;
private String description;
private LocalDateTime beginEnrollmentDateTime;
private LocalDateTime closeEnrollmentDateTime;
private LocalDateTime beginEventDateTime;
private LocalDateTime endEventDateTime;
private String location; // (optional) 이게 없으면 온라인 모임
private int basePrice; // (optional)
private int maxPrice; // (optional)
private int limitOfEnrollment;
}
추가 필드
private Integer id;
private boolean offline;
private boolean free; //요금 무료 여부
private EventStatus eventStatus = EventStatus.DRAFT;
EventStatus enum 추가
public enum EventStatus {
DRAFT, PUBLISHED, BEGIN_ENROLLMENT, CLOSED_ENROLLMENT, STARTED, ENDED
}
롬복 애노테이션 추가
- 롬복 플러그인 설치 필요
- Annotation Processors → Enable annotation processing 켜주자
@Getter @Setter @EqualsAndHashCode(of = "id")
@Builder @NoArgsConstructor @AllArgsConstructor
public class Event {
-
왜 @EqualsAndHasCode에서 of를 사용하는가
- 왜 @Builder를 사용할 때 @AllArgsConstructor가 필요한가
- @Data를 쓰지 않는 이유
- 애노테이션 줄일 수 없나
롬복 애노테이션
컴파일 시점에 클래스를 컴파일할 때 추가적인 코드가 추가된다.
컴파일된 클래스 확인 (target/classes/path/of/packages/events/Event.class
)
-
모든 파라미터를 갖고 있는 생성자가 Default 생성자로 생성됨. public이 아님
→ 다른 패키지에서 이 객체를 만들기 힘들다.
Event(java.lang.Integer id, java.lang.String name, java.lang.String description, java.time.LocalDateTime beginEnrollmentDateTime, java.time.LocalDateTime closeEnrollmentDateTime, java.time.LocalDateTime beginEventDateTime, java.time.LocalDateTime endEventDateTime, java.lang.String location, int basePrice, int maxPrice, int limitOfEnrollment, boolean offline, boolean free, dev.solar.demoinflearnrestapi.events.EventStatus eventStatus) { /* compiled code */ }
- static class EventBuilder 와 이 빌더 클래스를 반환하는 static 메서드인
builder()
도 만들어져 있다.
public static dev.solar.demoinflearnrestapi.events.Event.EventBuilder builder() { /* compiled code */ }
public static class EventBuilder {
private java.lang.Integer id;
private java.lang.String name;
private java.lang.String description;
//..
}
테스트 코드 작성
assertj
의 Assertions를 사용 (junit이 제공하는 Assertions 사용 X)
확인 사항
-
도메인에 대한 빌더
-
자바빈 스팩에 준수하는지
-
자바빈 스팩?
-
기본 생성자로 객체 생성 가능
-
각각의 모든 필드에 대해서 Getter, Setter가 있어야 한다.
⇒
롬복
으로 기능 구현
-
- 빌더확인
public class EventTest {
@Test
@DisplayName("빌더가 있는지 확인")
public void builder() {
Event event = Event.builder().build(); //build()하면 Event를 만들어줘야함
assertThat(event).isNotNull();
}
}
→ 빌더 없음.
빌더 추가 : Event 클래스에 @Builder
추가
- 기본 생성자로 객체를 만들 수 있어야 한다.
롬복 @Builder를 추가하면 빈 파라미터 기본 생성자가 생성되지 않고, 모든 파라마터가 들어간 생성자가 public이 아닌 지정자로 default로 생성된다. → 다른 패키지에서 이 객체를 만들기 힘들다.
→ 빌더 추가 : @Getter
, @Setter
, @Builder
, @NoArgsConstructor
, @AllArgsConstructor
@EqualsAndHashCode(of = "id")
추가
@Test
@DisplayName("자바빈 스팩에 준하는지 확인")
public void javaBean() {
// Given
String name = "Event";
String description = "Spring";
// When
Event event = new Event();
event.setName(name);
event.setDescription(description);
// Then
assertThat(event.getName()).isEqualTo(name);
assertThat(event.getDescription()).isEqualTo(description);
}
확인 사항
왜 @EqualsAndHasCode에서 of를 사용하는가
equals()와 hashCode()를 구현할 때, 기본적으로 모든 필드값들을 사용한다.
추후 엔티티간의 연관관계가 있을 때, 서로 상호참조하는 관계가 된다면 equals()와 hashCode() 코드 내에서 stack overflow가 발생할 수 있다. (상호참조 stack overflow ?)
따라서 특정 필드값(ex. id)만 해시코드로 만들어서 비교하도록 사용할 수 있다.
※ 필드값으로 다른 엔티티관련 값은 사용하지말자. 다른 엔티티와의 묶음을 만드는 것은 좋지 않다.
왜 @Builder를 사용할 때 @AllArgsConstructor가 필요한가
@Builder
를 사용할 때 @NoArgsConstructor
뿐만 아니라 손수 만든 다른 생성자가 있다면, 그 때도 모든 필드를 가지는 생성자가 필요하다.
일단, 의미 상 빌더 패턴을 생각해 보았을 때, 아무것도 없는 생성자는 필요가 없다.
빌더는 필드의 초기화 작업을 도와주는 역할이기 때문에.
Lombok 공식사이트 - @Builder 설명 中
Finally, applying @Builder to a class is as if you added @AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the @Builder annotation to this all-args-constructor. This only works if you haven’t written any explicit constructors yourself.
또한, 위 내용으로 보았을 때
@Builder 사용 시, 생성한 생성자가 없다면 @AllArgsConstructor(access = AccessLevel.PACKAGE) 가 암묵적으로 적용된다고 한다. 반대로 생성한 생성자가 있다면 @AllArgsConstructor 적용이 반드시 필요한게 아닐까?.. (이건 추측)
공식사이트 내 ‘Vanilla Java’ 부분의 실제 코드를 보면 (아래 코드)
builder()에서 BuilderExampleBuilder() 생성자를 반환하고,
BuilderExampleBuilder() 내부에서는 모든 매개변수에 대해 셋터용 메서드를 만들고 있다. (name, age)
이에 @Builder 내에서는 모든 파라미터를 갖는 생성자를 필요로 한다.
public static BuilderExampleBuilder builder() {
return new BuilderExampleBuilder();
}
public static class BuilderExampleBuilder {
private long created;
private boolean created$set;
private String name;
private int age;
private java.util.ArrayList<String> occupations;
BuilderExampleBuilder() {
}
public BuilderExampleBuilder created(long created) {
this.created = created;
this.created$set = true;
return this;
}
...
public BuilderExampleBuilder name(String name) {
this.name = name;
return this;
}
public BuilderExampleBuilder age(int age) {
this.age = age;
return this;
}
...
※ References
@Data를 쓰지 않는 이유
@Data 애노테이션은 equals()와 hashCode()도 같이 구현해주기 때문에 상호참조 문제가 발생할 위험이 있다.
롬복 애노테이션은 메타 애노테이션1으로 동작하지 않기 때문에, 커스텀해서 사용할 수 없다.
애노테이션 줄일 수 없나
롬복 애노테이션은 메타 애노테이션1으로 동작하지 않기 때문에, 줄일 수 없다.
Book 에 Writer가 있고 Writer에서 List