본문 바로가기
Blog/TIL

2024-02-24 (월)

by 코젼 2025. 2. 24.
728x90
반응형

2025-02-24 (월)

🎯 오늘의 핵심 정리

✅ DTO 조회 시 selectNew를 사용하여 성능 최적화

✅ JOIN이 일반적으로 더 빠르며, 서브쿼리는 꼭 필요한 경우에만 활용

✅ fetchOne()은 유일한 결과가 보장될 때만 사용하고, 다수의 결과 가능성이 있으면 fetchFirst() 사용

✅ null 처리는 ?:(엘비스 연산자)를 활용하여 안전하게 기본값 적용

✅ 테스트 코드 리팩토링을 통해 중복을 줄이고 가독성을 높이는 것이 중요함

📌 TIL (Today I Learned) - Kotlin + JDSL 활용 및 테스트 최적화


1️⃣ 리스트 조회 시 findAll()의 반환값 처리

  • 조회 결과가 없을 경우 null이 아닌 emptyList() 반환
  • emptyList()를 반환하면 null 체크 없이 안전하게 컬렉션 연산 (map, forEach) 수행 가능
val results = repository.findAll() ?: emptyList()
println(results.size) // 결과가 없으면 0

✔ 컬렉션 반환 시 null을 방지하고 emptyList()를 기본값으로 적용하는 것이 안전함.

2️⃣ 데이터 조회 방식: select vs selectNew

🔹 엔티티 조회 (select)

  • JPA 영속성 컨텍스트에서 관리됨
  • Dirty Checking 가능 (조회 후 수정 시 자동 반영)
  • 필요 없는 필드까지 조회될 가능성이 있음
val query = queryFactory.select(entity(DomainEntity::class))
    .from(entity(DomainEntity::class))
    .where(col(DomainEntity::identifier).equal(identifier))

🔹 DTO 조회 (selectNew)

  • JPA 영속성 컨텍스트에서 관리되지 않음
  • 불필요한 엔티티 필드를 로드하지 않으므로 성능 최적화 가능
  • 조회한 데이터는 변경해도 DB에 반영되지 않음
data class DomainDTO(val id: Long, val name: String)

val query = queryFactory.selectNew<DomainDTO>(
    col(DomainEntity::identifier),
    col(DomainEntity::name)
).from(entity(DomainEntity::class))

✔ API 응답용 DTO가 필요할 경우 selectNew를 사용하여 엔티티 로딩을 방지하는 것이 바람직함.

3️⃣ JOIN vs Subquery 차이점

비교 항목 JOIN Subquery

사용 목적 여러 테이블을 결합하여 조회 특정 값을 서브쿼리로 가져오기
조회 방식 조인된 테이블에서 데이터 조회 서브쿼리 결과를 컬럼으로 반환
성능 일반적으로 빠름 성능이 떨어질 가능성 있음
데이터 양 여러 행 반환 가능 단일 컬럼 또는 값 반환

✔ 기본적으로 JOIN을 사용하고, 서브쿼리는 꼭 필요한 경우에만 활용하는 것이 성능적으로 유리함.


4️⃣ fetchOne()과 fetchFirst()의 차이점

  • fetchOne(): 결과가 0개 또는 1개인 경우에만 사용 (2개 이상이면 NonUniqueResultException 발생)
  • fetchFirst(): 여러 개의 결과 중 첫 번째 값을 가져옴 (예외 발생 없음)
val result = queryFactory.select(entity(DomainEntity::class))
    .from(entity(DomainEntity::class))
    .where(col(DomainEntity::identifier).equal(identifier))
    .fetchOne() // 결과가 없으면 null, 2개 이상이면 예외 발생

✔ fetchOne()은 유일한 결과가 보장될 때 사용, 여러 개 가능성이 있으면 fetchFirst() 활용.

5️⃣ null 처리 및 안전한 기본값 적용

  • 데이터가 없을 경우 기본값을 설정하여 NPE(Null Pointer Exception)를 방지
  • 엘비스 연산자(?:)를 사용하여 기본값 제공
val domainData = domainService.findDomainByIdentifier(identifier)
    ?: DomainDTO(id = 0, name = "Default")

✔ null을 직접 반환하기보다는 기본값을 설정하는 것이 안전한 코드 작성 방법.

6️⃣ 테스트 코드 리팩토링 및 최적화

  • Mock 설정 중복 제거 → setUpMocks() 함수 활용
  • 반환값 비교 코드 단순화 → expectedResponse 변수 활용
  • 테스트 메서드 가독성 향상
fun setUpMocks(
    domainData: DomainDTO? = null,
    domainExists: Boolean = true
) {
    every { domainRepository.findByIdOrNull(any()) } returns if (domainExists) domain else null
    every { domainService.findDomainByIdentifier(any()) } returns domainData
}

it("정상적인 도메인 ID가 주어진 경우") {
    val domainData = DomainDTO(id = 1, name = "Test Domain")
    setUpMocks(domainData)

    val expectedResponse = DomainDetailDTO.Response(
        id = domainData.id,
        name = domainData.name
    )

    domainDetailService.findDomainByIdentifier(identifier) shouldBe expectedResponse
}

✔ 테스트 코드의 유지보수성을 높이고, 가독성을 개선함.

728x90
반응형

'Blog > TIL' 카테고리의 다른 글

2025-02-28 (금)  (1) 2025.02.28
2025-02-25 (화)  (0) 2025.02.25
2025-02-21 (금)  (0) 2025.02.21
2025-02-20 (목)  (0) 2025.02.20
2025-02-19 (수)  (0) 2025.02.19

댓글