인프런 커뮤니티 질문&답변

leesh5000님의 프로필 이미지

작성한 질문수

JPA 자식 엔티티를 저장/삭제하는 방법

23.12.08 11:32 작성

·

591

·

수정됨

0

안녕하세요.

JPA 강의를 수강하고, 실무에서 적용해보던 중 의문점이 생겨 질문드립니다.

강의에서는 엔티티의 변경 시에는 JPA의 Dirty Checking을 사용하라고 하셨는데요.

 

그렇다면 실제 JPA를 사용하는 실무에서도 새로운 하위 엔티티를 DB에 저장/삭제해야하는 경우에 상위 엔티티에서 하위 엔티티를 컬렉션에 add/remove 하는 방식을 통해 변경감지를 이용하여 저장하나요?

(이 경우에 자식 엔티티를 add 하기 위해 모든 자식 엔티티들을 Lazy Loading 하는 비효율이 발생할 것 같아 질문 드립니다.)

 

설명하기 쉽게 코드와 함께 질문을 정리해보겠습니다.

엔티티 코드는 다음과 같습니다. (편의 상 최대한 간추렸습니다.)

@Entity
public class Team {   
    @Id
    private Long id;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public void addMember(Member member) {
        this.members.add(member); <-- 자식 엔티티들을 모두 조회하는 LAZY LOADING 발생!
   }
}

@Entity
public class Member {
    @Id
    private Long id;

    @JoinColumn(name = "team_id")    
    @ManyToOne()
    private Team team;
}

 

<트랜잭션 계층>

public void createMember(Member newMember) {     
    Team team = teamRepository.findById(1L);     
    team.addMember(newMember);  <-- 자식 엔티티들을 모두 조회하는 LAZY LOADING 발생! 
} 

 

이 경우 변경감지를 통해서 하위 엔티티를 저장할 수 있고, 더 객체지향 관점에 맞는 방법 같지만 Member 하나를 추가하기 위해 Team에 속한 모든 Member를 조회해오는 비효율이 발생할 것 같습니다.

 

setTeam(team)으로 team과 연관관계를 맺어준 뒤, memberRepository.save(member)로 저장하면, 불필요하게 모든 member를 조회하지 않아도 될 것 같은데, 실무에서는 어떤 방법으로 엔티티를 저장/삭제하는 지 궁금해서 질문드립니다!

 

마찬가지로 연관관계 메서드도 자식엔티티와의 연관관계를 맺기 위해 자식엔티티들을 모두 LAZY LOADING (members.add(team) 시 자식 엔티티들을 모두 조회)해야 하나요?

 

감사합니다.

 

 

답변 1

0

인프런 AI 인턴님의 프로필 이미지

2023. 12. 09. 11:37

안녕하세요, 인프런 AI 인턴입니다.

개발맨님, JPA에서 새로운 하위 엔티티를 저장하거나 삭제할 경우, 질문에서 언급하신 것처럼 컬렉션에 add/remove를 통해 변경 감지를 사용하는 방법은 하나의 옵션입니다. 하지만 실제로 LAZY LOADING으로 인한 성능 이슈를 고려해야 하는 경우가 많습니다.
CrudRepository.save(…) 메서드는 엔티티가 아직 영속화되지 않은 경우 entityManager.persist(…) 메서드를 호출하여 저장하고, 이미 영속화된 경우 entityManager.merge(…) 메서드를 호출하여 엔티티를 병합합니다(Spring Data JPA 문서 참조). 이 메소드를 사용하여 각각의 엔티티를 개별적으로 저장함으로써 LAZY LOADING을 트리거하지 않는 접근 방식을 선택할 수도 있습니다.

실무에서는 상황에 따라 적절한 접근을 선택해야 하는데, 자식 엔티티를 개별적으로 저장하고 삭제하기 위해 명시적으로 savedelete를 호출하는 것이 더 바람직할 수 있습니다. 특히, 대량의 데이터를 처리해야 할 때는 JPA의 벌크 연산을 사용하여 성능을 최적화할 수 있습니다. 예를 들어, Spring Data JPA에서는 @Modifying어노테이션과 함께 @Query를 사용하여 벌크 삭제를 수행할 수 있습니다(Derived Delete Queries 문서 참조).

interface UserRepository extends Repository<User, Long> {
    @Modifying
    @Query("delete from User u where u.role.id = ?1")
    void deleteInBulkByRoleId(long roleId);
}

이러한 벌크 연산은 개별 엔티티에 대한 변경 감지나 라이프사이클 이벤트를 트리거하지 않기 때문에 성능이 중요한 상황에서 유용할 수 있습니다.

부모 엔티티의 컬렉션에서 자식 엔티티를 추가하거나 제거할 때 LAZY LOADING으로 인한 모든 자식 엔티티의 로딩 문제를 완전히 피하기는 어렵지만, 자식 엔티티가 많지 않은 경우나 성능에 큰 영향을 미치지 않는 경우에는 객체지향적인 설계 관점에서 변화를 감지하는 방식을 사용하는 것이 코드의 명확성과 유지보수성을 높일 수 있습니다.

최종적으로 채택할 방법은 애플리케이션의 요구사항, 성능 목표, 유지보수성 및 팀의 경험에 따라 결정되어야 합니다. 따라서 두 가지 접근 방식 모두 고려하여 그 장단점을 비교한 뒤 상황에 맞는 최적의 해결책을 선택하는 것이 중요합니다.

실무에서의 경험을 공유하는 것도 도움이 될 수 있으니 커뮤니티나 동료 개발자와 교류하며 노하우를 쌓는 것도 추천합니다.

감사합니다.