[Practice-2] 연관관계 편의 메서드

2025. 8. 31. 20:28·Spring/JPA

JPA 양방향 연관관계, '편의 메서드'는 선택이 아닌 필수!

JPA에서 양방향 연관관계 매핑은 매우 편리하지만, 제대로 관리하지 않으면 객체와 데이터베이스 간의 데이터 불일치를 유발하는 미묘한 버그의 원인이 될 수 있습니다. 이때 '연관관계 편의 메서드'는 이런 문제를 해결하고 코드를 더욱 객체지향적으로 만들어주는 중요한 개념입니다.

🤔 문제 상황: 왜 편의 메서드가 필요한가?

UserEntity(회원)와 PostEntity(게시글)가 서로 양방향 관계(1:N)로 매핑되어 있다고 가정해 봅시다.

  • UserEntity는 자신이 작성한 글 목록(List<PostEntity>)을 가집니다.
  • PostEntity는 자신의 작성자(UserEntity)를 가집니다.

새로운 게시글을 등록할 때, 우리는 다음과 같이 연관관계를 설정해야 합니다.


// 1. 새로운 유저와 게시글 객체 생성
UserEntity user = new UserEntity(...);
PostEntity post = new PostEntity(...);

// 2. 연관관계 설정
post.setUserEntity(user);       // (1) 게시글에 작성자 설정 (관계의 주인)
user.getPostEntity().add(post); // (2) 유저의 게시글 목록에도 추가
        

JPA는 관계의 주인(Owner)인 PostEntity의 userEntity 필드(@JoinColumn이 있는 곳)를 기준으로 DB에 외래 키를 저장합니다. 따라서 코드 (1)번만 실행해도 DB에는 정상적으로 데이터가 저장됩니다.

하지만 여기서 심각한 문제가 발생합니다. 만약 코드 (1)번만 실행하고 (2)번을 깜빡 잊는다면 어떻게 될까요?

  • 데이터베이스 상태: post 테이블의 user_id 컬럼은 정상적으로 저장됩니다. (정상 ✅)
  • 객체(메모리) 상태: user 객체의 postEntity 리스트는 여전히 텅 비어있습니다. (비정상 ❌)

이렇게 되면 DB와 메모리(객체)의 상태가 달라지는 데이터 불일치가 발생합니다. 이 상태에서 만약 게시글을 저장한 직후 user.getPostEntity()를 호출하면, 방금 저장한 게시글이 보이지 않는 어처구니없는 버그를 만나게 됩니다.

✨ 해결책: 연관관계 편의 메서드

연관관계 편의 메서드는 이런 개발자의 실수를 원천적으로 방지하기 위해, 양쪽의 값을 한 번에 설정해주는 메서드입니다. "이 메서드 하나만 호출하면 양쪽 연관관계 설정은 완벽하게 끝!"이라고 보장해주는 것이죠.

1. PostEntity (주인) 쪽에 편의 메서드 추가하기

PostEntity에 작성자를 설정할 때, 파라미터로 받은 UserEntity의 게시글 목록에도 자기 자신을 추가하도록 만드는 방법입니다.


// PostEntity.java
@Entity
public class PostEntity extends BaseEntity {

    // ... 기존 필드 ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private UserEntity userEntity;

    // ... 기존 빌더 ...

    // == 연관관계 편의 메서드 == //
    public void setUserEntity(UserEntity userEntity) {
        // 1. 기존 userEntity와의 관계를 제거 (만약 있다면)
        if (this.userEntity != null) {
            this.userEntity.getPostEntity().remove(this);
        }

        // 2. 새로운 userEntity 설정
        this.userEntity = userEntity;

        // 3. userEntity의 게시글 목록에 현재 Post를 추가
        userEntity.getPostEntity().add(this);
    }
}
        

2. UserEntity (주인이 아닌 쪽)에 편의 메서드 추가하기

반대로 UserEntity에 게시글을 추가할 때, 게시글의 작성자를 자기 자신으로 설정하도록 만들 수도 있습니다. 어느 쪽에 만들지는 팀의 컨벤션이나 설계에 따라 선택하면 됩니다.


// UserEntity.java
@Entity
public class UserEntity extends BaseEntity {

    @OneToMany(mappedBy = "userEntity")
    private List<PostEntity> postEntity = new ArrayList<>();

    // == 연관관계 편의 메서드 == //
    public void addPost(PostEntity post) {
        this.postEntity.add(post); // 1. 내(User) 글 목록에 추가
        post.setUserEntity(this);  // 2. 게시글(Post)의 작성자도 나로 설정
    }
    // ...
}
        

✅ 편의 메서드 사용하기

이제 서비스 코드에서는 이 편의 메서드만 호출하면 되므로, 코드가 간결해지고 실수를 할 여지가 사라집니다.


// PostService.java (수정 후)
@Transactional
public void createPost(Long authorId, PostCreateRequestDto dto) {
    UserEntity author = userRepository.findById(authorId)
            .orElseThrow(() -> new UsernameNotFoundException("존재하지 않는 회원입니다."));

    PostEntity post = dto.toEntity(); // DTO를 일단 post로 변환

    // 편의 메서드 호출! 이 한 줄로 양쪽 관계 설정이 모두 끝납니다.
    post.setUserEntity(author);

    postRepository.save(post);
}
        

🎯 핵심 요약

  • 목적: 양방향 연관관계에서 데이터 불일치와 개발자의 실수를 막기 위해 사용합니다.
  • 역할: 관계의 양쪽(user.getPosts()와 post.getUser())에 값을 한 번에 모두 설정해주는 역할을 합니다.
  • 결론: 양방향 매핑을 사용한다면, 편의 메서드를 만드는 것은 거의 필수적인 습관으로 생각하는 것이 좋습니다.

 

 

🎯 편의 메서드 위치를 정하는 기준

1. 객체가 주로 생성되고 관리되는 위치 ⭐️

가장 중요한 기준입니다. 어떤 객체를 기준으로 연관관계를 맺는 로직이 주로 실행되나요?

  • UserEntity가 기준일 때: 한 명의 유저 정보에 여러 게시글을 한꺼번에 추가하거나 변경하는 경우 (e.g., 관리자 페이지에서 유저의 게시글을 일괄 수정할 때)
  • PostEntity가 기준일 때: 하나의 게시글을 생성하면서 기존에 있던 유저와 연결하는 경우 (e.g., 사용자가 새 글을 작성할 때)

NearBuy 프로젝트에서는 "새로운 게시글을 등록하는" 흐름이 훨씬 더 일반적입니다. 따라서 PostEntity가 생성될 때 UserEntity를 설정하는 로직이 핵심이므로, PostEntity에 편의 메서드를 두는 것이 코드의 흐름과 일치합니다.

2. 관계의 주인 (Owner) 쪽

  • JPA에서 외래 키(@JoinColumn)를 관리하는 쪽을 '관계의 주인'이라고 합니다. 현재는 PostEntity가 주인입니다.
  • 관계의 주인이 직접 관계를 설정하는 로직을 갖는 것이 더 명확하다고 보는 관점이 많습니다. 주인이 아닌 UserEntity는 mappedBy를 통해 "나는 이 관계에 엮여있을 뿐, 직접 관리하지 않아"라고 선언한 셈이니까요.

3. 도메인 로직의 응집도

  • "게시글은 작성자를 가져야 한다"는 규칙은 게시글(Post) 도메인의 핵심 규칙입니다.
  • 따라서 PostEntity가 자신의 작성자를 설정하는 책임을 온전히 갖는 것이 객체지향적으로 더 응집도 높은 설계입니다.

'Spring > JPA' 카테고리의 다른 글

[Practice-6] 단뱡향/양방향 선택 기준 (⭐⭐⭐)  (0) 2025.12.25
[Practice-5] 일대다/다대일 관계 정의  (0) 2025.12.16
[Practice-4] Spring Data: 무한 깊이 조회 패턴 (댓글/대댓글, 카테고리)  (0) 2025.12.16
[Practice-3] Spring Data: Enum 고도화  (0) 2025.12.16
[Practice-1] JPA 연관관계 매핑과 성능 최적화: 흔한 오해와 올바른 접근법 (⭐⭐)  (0) 2025.08.28
'Spring/JPA' 카테고리의 다른 글
  • [Practice-5] 일대다/다대일 관계 정의
  • [Practice-4] Spring Data: 무한 깊이 조회 패턴 (댓글/대댓글, 카테고리)
  • [Practice-3] Spring Data: Enum 고도화
  • [Practice-1] JPA 연관관계 매핑과 성능 최적화: 흔한 오해와 올바른 접근법 (⭐⭐)
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (241) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (16)
        • MSA 기본 (11)
        • MSA 아키텍처 (5)
      • Kafka (30) N
        • Core (18) N
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[Practice-2] 연관관계 편의 메서드
상단으로

티스토리툴바