문제 상황
최범균님의 도메인 주도 개발 시작하기 책을 읽으며 기존 프로젝트를 리팩토링 하던 중 문제에 직면했습니다.
StudyEntity (study) 는 StudyApply 벨류 타입을 컬렉션으로 가지고 있으며 해당 벨류 타입을 별도 테이블로 (apply) 생성했습니다.
문제는 기능 요구사항 중 apply 테이블과 study 테이블을 조인해 값을 가져와야 하는 것이었습니다
JPQL에서는 컬렉션으로 지정된 StudyApply의 필드값에 접근할 수 없기 때문에 네이티브 쿼리문 + @SqlResultSetmapping을 이용해 문제를 해결했습니다.
StudyEntity
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "study")
public class StudyEntity extends BaseTimeEntity {
@Id
@Column(name = "study_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "study_apply", joinColumns = @JoinColumn(name = "study_id"))
private List<StudyApply> studyApplies;
. . .이하 생략
StudyApply
@Embeddable
@Getter
@NoArgsConstructor
public class StudyApply {
@Enumerated(EnumType.STRING)
private Inspection inspection;
private String introduce;
@Column(name = "user_id")
private Long userId;
@Column(name = "reject_reason")
private String rejectReason;
. . . 이하 생략
해결
아래 코드와 같이 엔티티에 @SqlResultSetmapping 어노테이션을 붙인 뒤 파싱할 객체의 값을 지정해줍니다.
@SqlResultSetMapping(
name = "StudyApplyDaoByUserIdMapping",
classes = @ConstructorResult(
targetClass = StudyApplyDaoByUserId.class,
columns = {
@ColumnResult(name = "study_id", type = Long.class),
@ColumnResult(name = "title", type = String.class),
@ColumnResult(name = "inspection", type = Inspection.class),
@ColumnResult(name = "introduce", type = String.class)
}
)
)
이 때 지켜야할 조건이 두가지 있습니다.
1. @SqlResultSetMapping 어노테이션은 엔티티 클래스에만 적용할 수 있습니다.
2. targetClass에 파싱하려는 값들을 가지고있는 생성자가 있어야 합니다.
이후 네이티브 쿼리문을 작성한 뒤, 위에서 지정한 name 값인 StudyApplyDaoByUserIdMapping을 createNativeQuery 메서드의 파라미터로 지정해줍니다.
@Override
public List<StudyApplyDaoByUserId> findApplyByUserId(Long userId) {
String sql = "SELECT s.study_id, s.title, sa.inspection, sa.introduce " +
"FROM study AS s INNER JOIN study_apply as sa ON s.study_id = sa.study_id " +
"WHERE sa.user_id = ?1";
Query query = em.createNativeQuery(sql, "StudyApplyDaoByUserIdMapping");
query.setParameter(1, userId);
return (List<StudyApplyDaoByUserId>) query.getResultList();
}
테스트 결과
@Test
void 회원이_신청한_스터디조회() {
StudyEntity study = StudyEntityFixture.makeStudyEntity(1L);
StudyApply apply = study.getStudyApplies().get(0);
studyRepository.save(study);
List<StudyApplyDaoByUserId> dao = studyRepository.findApplyByUserId(1L);
assertAll(
() -> assertEquals(dao.size(), 1),
() -> assertEquals(dao.get(0).getStudyTitle(), study.getStudyInfo().getTitle()),
() -> assertEquals(dao.get(0).getInspection(), apply.getInspection()),
() -> assertEquals(dao.get(0).getIntroduce(), apply.getIntroduce())
);
}
결론
네이티브 쿼리와 @SqlResultSetmapping 어노테이션을 이용해 엔티티에 있는 벨류 컬렉션을 조회해 보았습니다.
현재는 벨류 컬렉션인 apply 테이블을 조인해서 가져오는 기능이 한가지만 있기 때문에 StudyApplyDaoByUserId로 파싱해도 문제가 없습니다.
하지만 apply 테이블을 조인해서 가져오는 기능이 여러개가 되고 dao를 여러개 만들 경우 엔티티 클래스 위에 @SqlResultSetmapping 어노테이션을 여러개 붙여야 하는 상황이 발생하게되고, 코드 가독성에 어려움이 생길 것 입니다.
이러한 문제에 직면할 경우 조인한 두 테이블에 있는 칼럼들을 모두 가져오는 dao로 데이터를 조회한 뒤 재 파싱 해주는 방식을 이용해야겠다 생각됩니다.
'개발 > SpringBoot' 카테고리의 다른 글
S3 presigned Url 적용을 통한 영상 업로드 성능 향상 (1) | 2024.06.19 |
---|---|
SpringDoc Swagger Https 설정법 (0) | 2024.04.11 |
멀티 프로세스 환경 DB 동시성 제어 (0) | 2024.01.05 |
[StudyHub] unknown column '필드명' in 'field list' 에러 해결 (0) | 2023.12.29 |
[StudyHub] 조회 메서드 반환 객체 분리 (0) | 2023.12.18 |