@MappedSuperclass 어노테이션을 조금 다르게 사용하는 방법에 대한 포스팅입니다.
해당 어노테이션의 핵심 기능은 '공통 매핑 정보를 간편하게 관리'하기 위해서 인데요.
때문에 프로젝트에서는 일반적으로 createdDate와 modifiedDate 같은 공통 필드를 관리하는 BaseTimeEntity 클래스에서 가장 많이 사용됩니다.
아래 내용은 BaseTimeEntity가 아닌 @MappedSuperclass 어노테이션을 활용해 '완전히 똑같은 필드를 가진, 같은 종류지만 서로 다른 Entity를 하나의 Repository로 관리하는 방법'을 정리한 포스팅입니다.
단순하게 공통 매핑 정보를 관리하는 것과는 조금 다르게 사용해보았기 때문에 내용 참고해보시고 필요에 따라 적용을 고려해보시면 될 것 같습니다.
(좋은 방법이라서 사용한 것이라기보다는 이렇게도 사용해보고 싶어서 구현해 본 내용임을 미리 참고 부탁드립니다.)
@MappedSuperclass
해당 어노테이션은 공통 매핑 정보가 필요할 때 활용되는 어노테이션입니다.
위에서 예를 든 것처럼 프로젝트에서 많은 테이블들은 createDate라는 생성 날짜를 공통으로 가지고 있습니다. 각각의 Entity에 모두 createDate를 만들어서 사용할 수도 있지만, 중복된 코드는 최대한 줄여주는 것이 유지보수 측면에서 좋기 때문에 이러한 기능이 생긴 것인데요.
@MappedSuperclass 어노테이션을 적용한 클래스의 경우 테이블과 직접 매핑되지 않고, '상속받는 자식 클래스에게 매핑 정보만을 제공'하게 되는데요.
'부모, 자식 간의 상속관계 매핑이 아니라 매핑 정보 제공을 위한 것'과 '테이블과 매핑되지 않는다는 것'이라는 핵심을 통해 해당 어노테이션의 사용 목적을 이해하면 좋을 것 같습니다.
또한 직접 사용되는 경우가 없기 때문에 추상 클래스로 만드는 것이 권장되고 있습니다.
@MappedSuperclass
public abstract class BaseItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "name", nullable = false)
private String name;
}
(BaseItem abstract class)
@Table(name = "item_a)
@Entity
public class ItemA extends BaseItem { }
(ItemA)
@Table(name = "item_b")
@Entity
public class ItemB extends BaseItem{ }
(ItemB)
예를 들어서 다음과 같이 완전히 같은 필드를 가지며 용도도 같은 두 Entity를 따로 관리하고 싶을 경우가 있을 수 있습니다.
이런 경우 하나의 엔티티에서 타입 필드를 주고 관리할 수도 있지만, 다음과 같은 구현이 필요한 부분이라 이렇게 구현하게 되었는데요.
이렇게 구현된 경우 위에서 설명한 것처럼 테이블은 'item_a' 테이블과 'item_b' 테이블을 따로 사용하게 됩니다.
두 테이블을 따로 사용하지만 사용되는 기능은 완전하게 똑같다는 가정 하에 작업된 것인데요.
맨 위에서 언급했던 것처럼 이렇게 구현한 최종 목적은 '하나의 Repository에서 같은 기능을 가진 두 개의 Entity를 모두 처리'하기 위함입니다.
(두 개 밖에 없을 때는 이렇게 구현하는 것이 비효율 적일 수도 있지만 해당되는 경우가 많아질수록 나중에 유지보수 측면에서의 장점을 가지고 있다고 생각합니다.)
Repository
@RequiredArgsConstructor
@Repository
public class BaseItemRepository {
private final JPAQueryFactory queryFactory;
QBaseItem qItemA = QItemA.itemA._super;
QBaseItem qItemB = QItemB.itemB._super;
public List<ItemDto> getItemAList() {
return queryFactory.select(Projections.constructor(ItemDto.class,
qItemA.id,
qItemA.name
))
.from(qItemA)
.fetch();
}
public List<ItemDto> getItemBList() {
return queryFactory.select(Projections.constructor(ItemDto.class,
qItemB.id,
qItemB.name
))
.from(qItemB)
.fetch();
}
}
(BaseItemRepository)
Q 클래스를 정의하는 부분에서의 핵심은 '_super'를 통해 상위 Q 클래스로 해당 Q 클래스를 캐스팅할 수 있다는 것인데요.
아래 메서드 부분을 getItemAList(), getItemBList()로 각각 구현한 것은 처리되는 것을 보여주기 위함이며, 실제 사용에서는 각각 타입에 따른 Enum 값을 주고 Enum 값에 맞는 QClass를 동적으로 선택하여 하나의 메서드에서 구현하는 것으로 처리할 수 있습니다.
이렇게 구현하게 되는 경우 앞으로 BaseItem를 상속받는 클래스가 추가되더라도 BaseItem 클래스를 상속받아 구현한 뒤, Repository에 해당 클래스의 Q 클래스에 대한 정의를 추가하고, Enum 타입만 추가하게 되면 같은 기능을 바로 사용할 수 있게 됩니다.
또한 유지보수가 일어났을 경우에도 효율적으로 처리가 가능하다는 장점이 있을 것 같습니다.
잘못된 부분이나 다른 의견은 댓글로 남겨주시면 참고하여 더 공부하도록 하겠습니다. 감사합니다.
'Programming > Java' 카테고리의 다른 글
Java - BigDecimal 사용하는 이유 (feat.부동소수점의 부정확성) (0) | 2022.08.18 |
---|---|
Java 위도 경도에 따른 거리 계산(내 주변 반경) (2) | 2022.08.03 |
Java 클라이언트 요청 IP 가져오는 방법(HttpServletRequest) (0) | 2022.06.07 |
LocalDateTime Jackson 직렬화 오류, 두 가지 해결 방법 (0) | 2022.06.04 |
Java QR코드 생성 (Image 출력 및 파일저장) (5) | 2022.05.28 |