카테고리 없음

@Embeddable 테이블

kjw81024 2026. 5. 25. 18:22

최종 프로젝트 구현하면서 거의....바이브코딩이라고 해도 될 정도로 AI 와 함께 코드를 짜다 보니까 처음 보는 문법이 보여서 정리하고 넘어가기


 1. @Embeddable 어노테이션

: 필드를 객체로 묶어서 관리하고 싶을 때 사용하는 클래스

@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class TimeSlotInfo {

    @Column(nullable = false)
    private LocalDate careDate;

    @Column(nullable = false)
    private LocalTime startTime;

    @Column(nullable = false)
    private LocalTime endTime;

    @Column(nullable = false)
    private Integer sequence;

    public static TimeSlotInfo of(LocalDate careDate, LocalTime startTime,
                                  LocalTime endTime, Integer sequence) {
        TimeSlotInfo info = new TimeSlotInfo();
        info.careDate = careDate;
        info.startTime = startTime;
        info.endTime = endTime;
        info.sequence = sequence;
        return info;
    }
}

사용처에서 이렇게 쓴다 

( TimeSlotInfo Class 는 CareRequestTimeSlot, PostTimeSlot, ReservationTimeSlot 세 클래스에서 embedded 해서 사용)

@Embedded
private TimeSlotInfo timeSlotInfo;

 

이렇게 작성하면 내가 TimeSlotInfo 안에 있는 field 들을 다 구현하지 않아도 @Embedded 해온 클래스 내부에서 접근 가능하다.

 

Abstract 클래스로 구현하면 사실 넣을 필요가 없음 

public class CareRequestTimeSlot extends TimeSlotInfo {

 

처음에는 세 클래스가 BaseEntity 를 상속하도록 만들고 있었기 때문에 abstract class 는 클래스당 하나만 상속 가능하다는 특징 때문에 Embedded 를 사용했다. 

  abstract @Embeddable
목적 공통 컬럼 상속
ex) createdAt 등 ,,,
필드 묶음을 객체화
관계 상속 (is-a) 포함 (has-a)
개수 클래스당 하나  여러개 가능 

지금은 BaseEntity 를 받고있지 않으니까 사실상 ... TimeSlotInfo class 를 abstract 로 바꿔서 extends 하도록 해도 전혀 상관 없다는 사실,......

하지만 뭐 의미상 공통 컬럼을 물려주는 개념이 아니라, TimeSlotInfo 내부 필드 묶음을 객체로 관리하기 위한 것이니까 @Embeddable 이 더 정확하긴 하다 


 

2. <? extends HasTimeSlot>

public interface HasTimeSlotInfo {
    TimeSlotInfo getTimeSlotInfo();
}

HasTimeSlotInfo 인터페이스는 getTimeSlotInfo() 메서드를 반드시 구현하도록 강제한다. 

public class CareRequestTimeSlot implements HasTimeSlotInfo {

인터페이스니까 implements 해서 사용함!

처음에 헷갈린 이유가 ... CareRequestTimeSlot 내부에 getTimeSlotInfo() 라는 method 가 없어서 음? 내가 아는 ... interface 구현이 아닌가 했는데 알고보니 Lombok 의 @Getter 어노테이션이 자동으로 넣어줘서 인터페이스 입장에서는 있다고 판단한것 

실제로는 구현되어있다.

@Override
public TimeSlotInfo getTimeSlotInfo() { 
    return this.timeSlotInfo;
}

Getter 안 쓰고 이르케 넣어줘도 됨 

 

또 헷갈렸던 부분이 이 메서드다.... 

생긴것부터 저 지피티가 만들었어요 하고 있음 ㅎ...

public boolean hasTimeConflict(List<? extends HasTimeSlotInfo> slotsA,
                               List<? extends HasTimeSlotInfo> slotsB)

매개변수 의미가 HasTimeSlotInfo를 implements한 애들의 List는 다 받겠다 << 임 

 

메서드 내부에서는 꺼낸 원소가 뭐든 간에 HasTimeSlotInfo 로 보기 때문에 getTimeSlotInfo() 를 꼭 구현하라고 HasTimeSlotInfo 에서 작성해둔것이였다 

for (HasTimeSlotInfo a : slotsA) {
    TimeSlotInfo infoA = a.getTimeSlotInfo();
}

요렇게 바로 사용 가능 

 

HasTimeSlotInfo 를 extends 한 이유는 저기에 Reservation에서 사용한 TimeSlot 이 들어오든... CareRequest에서 사용한 TimeSlot 이 들어오든 hasTimeConflict method 에서 재사용하기 위해서


3. ? vs Generic T

 

위에서 사용된 ? 가 조금 헷갈렸는데 

? (와일드카드) vs T (제네릭 타입 변수) 를 비교했을 때 

public <T extends HasTimeSlotInfo> List<T> filter(List<T> slots) {
    // 입력이 List<PostTimeSlot> 이면 반환도 List<PostTimeSlot>
}

제네릭 변수 T 는 타입에 이름을 붙여서 입력과 출력 타입을 연결할 때 사용하고

public boolean hasTimeConflict(List<? extends HasTimeSlotInfo> slotsA,
                               List<? extends HasTimeSlotInfo> slotsB) {
    // slotsA 가 뭔지, slotsB 가 뭔지 이름 붙일 필요 없음
    // 그냥 꺼내서 getTimeSlotInfo() 호출만 하면 됨
}

와일드 카드 ? 는 타입 이름이 필요 없을 때 사용한다.

 

내가 사용한 hasTimeConflict 메서드는 반환값도 boolean 이고 꺼내서 읽기만 하니까 타입 이름이 필요없다 -> ? 사용하기