❄️ 트러블 슈팅

[트러블슈팅] JPA Cascade와 Aggregate Root 적용하기

le2donguk 2026. 3. 22. 17:55

 

회원가입 기능을 구현하면서 여러 엔티티가 함께 생성되는 구조를 설계하게 되었습니다.

처음에는 각 엔티티를 개별 Repository를 통해 저장하는 방식으로 구현했지만 이 방식은 JPA를 사용하면서도 ORM의 장점을 제대로 활용하지 못하는 구조였습니다.

이번 글에서는 회원가입 로직을 Cascade와 Aggregate Root 개념을 활용하여 개선한 과정을 정리해보려고 합니다.

 


문제 상황

회원가입 시 다음과 같은 여러 엔티티가 함께 생성되었습니다.

Member
ProfileImage
Auth
Business
BusinessCode

 

기존 구현에서는 이렇게 필요한 각 엔티티를 Repository를 통해 각각 개별적으로 저장하였습니다.

코드를 보면 아래와 같습니다 

 

기존 회원가입 로직

@Transactional
public Long signUp(SignUpRequestDto request) {

    // 멤버 저장
    Member member = Member.of(request);
    member.setPassword(passwordEncoder.encode(request.getPassword()));
    Member savedMember = memberRepository.save(member);

    // 프로필 이미지 저장
    ProfileImage profileImage = ProfileImage.of(request.getProfileImageKey(), savedMember);
    profileImageRepository.save(profileImage);

    // 권한 저장
    Auth auth = new Auth();
    auth.addMember(savedMember);
    authRepository.save(auth);

    // 사업번호 조회
    BusinessCode businessCode =
            businessCodeRepository.findBusinessCodeByMinorName(request.getMinorName());

    // 사업체 저장
    Business business = Business.of(request);
    business.setBusinessCode(businessCode);
    business.setMember(savedMember);
    businessRepository.save(business);

    return savedMember.getId();
}

 


문제점

위 코드 구조에서 회원가입 API를 날리면 다음과 같이 여러 insert 쿼리가 발생했습니다.

member insert
profileImage insert
auth insert
business insert

 

회원가입이라는 하나의 행위에 대해
서비스 레이어가 모든 엔티티 저장 순서를 직접 관리하고 있었다. (N+1)

 

이로 인해 다음과 같은 문제가 발생하였습니다

1. 서비스 레이어 책임 증가

서비스 레이어가 다음 책임을 모두 가지게 됩니다.

  • 엔티티 관계 설정
  • 저장 순서 관리
  • 여러 Repository 호출

이는 비즈니스 로직보다 영속성 관리 코드가 더 많아지는 문제를 만들었습니다.

 

2.ORM 사용 이점 감소

JPA를 사용하고 있음에도 코드 스타일은 다음과 같이 됩니다.

엔티티 생성
→ repository.save()
→ 또 생성
→ repository.save()

이는 사실상 JDBC 스타일과 크게 다르지 않은 구조입니다.

 


원인 분석

문제의 핵심 원인은 연관 엔티티의 생명주기를 Member가 관리하지 않고 있었다는 점이었습니다.

즉 엔티티들이 모두 Member에 종속된 구조였습니다.

Member 삭제 → 함께 삭제
Member 생성 → 함께 생성
ProfileImage
Auth
Business

독립적인 Aggregate가 아니라 Member 내부 구성 요소에 가까운 구조였습니다.

따라서 Member가 Aggregate Root 역할을 하는 것이 자연스럽습니다.


해결 방법 — Cascade 적용

연관 엔티티의 생명주기가 동일하다면
CascadeType.ALL을 적용하여 Member 저장 시 함께 영속화되도록 설계할 수 있습니다.

이를 통해 다음과 같이 코드를 리팩토링을 할 수 있습니다

Member 엔티티 변경

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProfileImage> profileImages = new ArrayList<>();

@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Auth> auths = new ArrayList<>();

@OneToOne(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private Business business;

이렇게 하면

memberRepository.save(member)

한 번으로 모든 연관 엔티티가 함께 영속화됩니다.


엔티티 생성 책임을 Member로 이동

또한 엔티티 관계를 자연스럽게 설정하기 위해

정적 팩토리 메서드를 활용하였습니다.

public static Member of(SignUpRequestDto dto) {
    Member member = new Member();
    member.email = dto.getEmail();
    member.name = dto.getName();
    member.kakaoId = dto.getKakaoId();
    member.status = MemberStatus.ACTIVE;
    member.helpCount = 0;
    member.badge = MemberBadge.도움일꾼;
    member.fcmToken = "";
    return member;
}

 

 


최종적인 모습 - 회원가입 로직

@Transactional
public Long signUp(SignUpRequestDto request) {

    BusinessCode businessCode =
            businessCodeRepository.findBusinessCodeByMinorName(request.getMinorName());

    Member member = Member.of(request);
    member.setPassword(passwordEncoder.encode(request.getPassword()));

    Business business = Business.of(request);
    business.setBusinessCode(businessCode);
    business.setMember(member);

    Auth auth = new Auth();
    auth.addMember(member);

    memberRepository.save(member);

    return member.getId();
}

 


개선 효과

기존

repository.save() 여러 번 호출
저장 순서 서비스가 직접 관리

개선

memberRepository.save() 한 번 호출
↓
Cascade 전파
↓
연관 엔티티 자동 영속화

 

1. 진짜 ORM 스타일 코드

엔티티 관계 중심으로 로직이 작성되면서
JPA의 장점을 제대로 활용할 수 있게 되었습니다.

 

2. 서비스 로직 단순화

서비스 레이어는 이제

비즈니스 흐름 제어

에만 집중하고 영속성 전파는 엔티티가 담당하게 되었습니다.

 

3. Aggregate Root 설계 적용

Member
 ├ ProfileImage
 ├ Auth
 └ Business

Member가 명확한 Aggregate Root 역할을 하게 되었다.

이는 도메인 모델의 응집도를 높이고 향후 유지보수성을 크게 향상했습니다.

 


 마무리

처음에는 단순히 회원가입 기능을 구현하는 것이 목표였지만
리팩토링을 진행하면서 다음을 배울 수 있었습니다.

  • JPA Cascade는 단순 편의 기능이 아니라 도메인 설계 도구
  • 서비스에서 Repository를 많이 호출할수록 설계 신호를 의심해야 한다
  • Aggregate Root 개념은 실제 코드 품질에 직접적인 영향을 준다

회원가입처럼 여러 엔티티가 동시에 생성되는 기능이라면

한 번쯤 생명주기와 Aggregate 구조를 고민해 보는 것이 좋을 것 같습니다.

생각보다 코드가 훨씬 깔끔해진다 🙂