[SPRING] ⛓️ 연관관계(Cascade 설정), QueryDSL, N+1 방지

림민지·2025년 5월 2일

Today I Learn

목록 보기
53/62

🔁 Cascade란?

Cascade는 부모가 어떤 행동(예: 저장, 삭제)을 할 때, 자식에게도 그걸 자동으로 같이 해주는 기능

부모 객체를 저장할 때 → 자식도 자동으로 저장
부모를 삭제할 때 → 자식도 같이 삭제

✨ Cascade 종류

타입설명사용 예시
PERSIST부모를 저장하면 자식도 저장새로운 회원과 할 일을 같이 저장할 때
REMOVE부모를 삭제하면 자식도 삭제회원을 삭제하면 할 일도 삭제되게 할 때
MERGE부모의 변경사항을 자식에도 적용수정 시 함께 반영되도록 할 때
REFRESHDB에 있는 걸로 다시 초기화할 때 자식도 같이 초기화캐시 무효화 등에서
DETACH영속성 컨텍스트에서 부모와 자식을 같이 분리할 때
ALL위의 모든 걸 다 포함전부 다 같이 할 때, 주의 필요!

⚠ 고아 객체(Orphan Removal)란?

'고아 객체'는 말 그대로 더 이상 부모가 없는 자식 객체
만약 부모에서 자식을 제거했을 때, 자식이 혼자 남게 되면 어떻게 될까?

orphanRemoval = true

이러케 설정하면 → 그의 자식 객체도 자동으로 삭제!

하지만 ,,,,
❗ 자식이 여러 부모를 가질 수 있을 때는?
자식이 다른 부모에게도 연결되어 있는데, 한 부모에서 제거했다고 바로 삭제되면?

아직 필요한 자식인데 실수로 삭제될 수 있다 ㄷㄷㄷ EVA

📌 cascade = ALLorphanRemoval = true를 함께 쓸 때는 이 설정이 끼칠 영향을 항상 생각하기
(특히 자식이 여러 부모와 연결될 가능성이 있는 경우)


🧩 QueryDSL

QueryDSL은 SQL을 자바 코드처럼 작성하게 해주는 도구!

👉 마치 자바로 SQL을 짜는 너낌
→ 더 타입 안정성 있고, 자동완성도 되고, 실수도 줄어들 수 있땅
(기존 쿼리나 JPA는 스펠링 하나만 틀려도 안되는 경우가 정말 많았다ㅠㅠ)

QueryDSL 장점
자바 코드처럼 쿼리 작성 가능 → 오타 줄어듦!
컴파일 타임에 오류 잡을 수 있음
IDE 자동완성이 지원
JPA보다 복잡한 조건 검색을 더 깔끔하게 쓸 수 있다

🛠 QueryDSL 사용 전 설정 (Gradle 기준)

1. Gradle에 dependency 추가

dependencies {
    implementation "com.querydsl:querydsl-jpa"
    annotationProcessor "com.querydsl:querydsl-apt"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
  1. Q클래스 생성을 위한 플러그인 설정
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
    main.java.srcDirs += querydslDir
}

tasks.withType(JavaCompile) {
    options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

이 설정을 하면, QueryDSL이 Q클래스라는 걸 자동으로 만들어준다

🧱 설정 클래스 만들기 (JpaConfiguration 클래스)

@Configuration
public class JpaConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

@PersistenceContext
: JPA에서 DB랑 통신할 때 쓰는 EntityManager를 주입

JPAQueryFactory
: QueryDSL로 쿼리를 만들 수 있게 도와주는 도구!
→ 이걸 @Bean으로 등록하면, 다른 곳에서 @Autowired로 쉽게 쓸 수 있다

📦 Repository 코드 적용 예시

@Repository
@AllArgsConstructor
public class TodoCustomRepositoryImpl implements TodoCustomRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public Optional<Todo> findByIdWithUser(Long todoId) {
        QTodo qTodo = QTodo.todo;
        QUser qUser = QUser.user;

        Todo result = jpaQueryFactory.selectFrom(qTodo)
                .leftJoin(qTodo.user, qUser).fetchJoin()  // 연관된 유저도 한 번에 조회
                .where(qTodo.id.eq(todoId))               // 조건: id가 todoId와 같은 것
                .fetchOne();                               // 하나의 결과만 조회

        return Optional.ofNullable(result);
    }
}

QTodo, QUser는 QueryDSL이 만들어준 클래스! → 자동완성!!!

fetchJoin()은 연관된 객체도 한 번에 가져온다 (→ N+1 문제 방지!)

fetchOne()은 결과를 1개만 가져올 때 사용
(여러 개 가져올 땐 fetch() 사용)

profile
@lim_128

0개의 댓글