본문 바로가기
TIL

[231130] 팀과제 수정

by 진진리 2023. 11. 30.
728x90

팀 과제 수정

깃허브: https://github.com/dlwls423/hobby-bunage-2

 

GitHub - dlwls423/hobby-bunage-2: 팀과제 hobby bungae 수정

팀과제 hobby bungae 수정. Contribute to dlwls423/hobby-bunage-2 development by creating an account on GitHub.

github.com

API 명세: https://documenter.getpostman.com/view/30859017/2s9YeHZAAB

 

hobbybungae

The Postman Documenter generates and maintains beautiful, live documentation for your collections. Never worry about maintaining API documentation again.

documenter.getpostman.com

 

0. 기존 코드 문제점

  • 게시글을 작성할 때 게시글에 작성한 지역(State)이 새로 레포지토리에 저장되면서 DB 중복된 row가 저장됨
    • post 엔티티는 state 엔티티와 다대일 연관관계를 맺고 있으면서 CascadeType.PERSIST로 설정
    • 새로 post 객체를 저장할 때 영속성 컨텍스트에 사용자가 요청한 State가 없기 때문에 DB에 insert문이 날라감
      • -> 중복 저장 발생
    • 영속성 컨텍스트에 있다면 update문이 날라가게 된다.
  • 양방향, 지연로딩 적용으로 org.hibernate.LazyInitializationException 예외 발생
    • 주로 이 문제를 해결하기 위해 새로 코드를 수정해보는 시간을 가지게 되었다.

 

1. 엔티티 설정

 

처음에는 다대다 관계를 줄이고자 사용자와 취미의 관계를 
취미에 따른 사용자를 조회하는 기능이 없기 때문에 1:N으로 설정하였다.

그런데 나중에 취미를 삭제하는 기능에서 해당 취미를 참조하는 사용자나 게시글이 없는 경우에만 삭제할 수 있도록 하는 로직을 추가하면서 취미를 기준으로 사용자를 조회해야 하는 필요가 생겼다.

그래서 결국 처음과 같이 N:N 관계로 수정하였다.

 

  • 즉시 로딩? 지연 로딩?
    • 즉시 로딩을 페치 전략으로 사용하는 경우 연관된 엔티티가 컬렉션인 경우 로딩하는 데 비용이 많이 들고, 예상치 못한 SQL문이 여러 차례 발생할 수 있기 때문에 가급적이면 지연로딩을 사용하는 것이 좋다.
    • 이번에는 모든 페치 전략을 지연 로딩으로 설정했다.
    • 이후에 시간이 있으면 필요한 곳에는 즉시 로딩을 하도록 최적화해볼 예정이다.
  • 양방향? 단방향?
    • 모든 관계를 양방향으로 설정하였다.
    • 왜나하면 @OneToMany 단방향의 경우 다른 테이블에 외래키가 있어 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 하기 때문이다.
    • 양방향으로 매핑하는 경우 주의할 점 : 순환 참조
      • 이를 해결하기 위해서 Dto에서 직접 Entity를 참조하지 않으면 된다.
      • controller에서 응답하는 경우 dto를 JSON으로 직렬화를 수행하는 데 이 과정에서 dto의 Getter를 사용
      • 특정 Entity의 Getter를 사용할 때 연관관계의 entitty에 대한 Getter를 사용
      • 이때 양방향 연관관계 이므로 서로 Getter를 통해 계속 참조하면서 무한 참조가 발생

 

구현 코드 예시

Post와 Hobby는 다대다 관계이기 때문에 중간 테이블 PostHobby를 만들어 각각 일대다 연관관계로 설정해주었다.

Post와 PostHobby 테이블을 어떻게 구현했는지 설명하고자 한다.

  • Post

  • post가 삭제되면 posthobby도 삭제되도록 CascadeType.REMOVE를 걸어주었다.

 

  • 다대일 관계에 있는 state와 user는 setState와 setUser 매서드를 사용해서 연관관계를 설정하도록 하였다.
  • 일대다 관계에 있는 posthobby와 comment는 addPostHobby와 addComment 매서드를 사용해서 연관관계를 설정하도록 했다.

 

  • PostHobby

 

  • 다대일 관계에 있는 post와 hobby를 setPost, setHobby 매서드로 연관관계를 설정하도록 하였다.

 

  • 순환참조 문제 해결 : PostResponseDto 수정

 

  • Post 관련 기능에서 기능은 다 처리되었음에도 불구하고 response body에 내용이 뜨지않고 응답시간 계속 지속되는 오류가 발생했다.
  • 기존의 dto에서는 State state;를 필드로 가지면서 State entity를 직접 가지고 있었다.
  • 순환 참조 문제를 해결하는 방법 중 dto에서 직접 entity를 참조하지 않아야 한다는 글을 보고 String stateSi, stateSi, stateGu로 변경하였다.
  • 그러자 오류없이 해당 기능이 수행되었다.

 

2. 서비스 코드 수정

이전에 팀 과제를 할 때 LazyInitializationException 오류를 자주 보게되었다.

지연 로딩에 대해 조금 공부하고 나니 해당 오류에 대해 조금 이해할 수 있었다.

  • 지연로딩을 사용할 때... 프록시 객체?
    • 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이를 프록시 객체라고 함
  • 프록시 객체의 초기화
    • 프록시 객체는 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성
    • 초기화되면 프록시 객체를 통해 실제 엔티티에 접근할 수 있음
    • 초기화는 영속성 컨텍스트의 도움을 받아야 가능 = 영속 상태일 때 가능
    • 준영속 상태의 프록시를 초기화하면 하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킴
  • JPA에서 엔티티를 저장할 때...
    • 연관된 모든 엔티티는 영속 상태여야 한다 !
모든 엔티티를 양방향으로 설정했기 때문에 특정 엔티티를 저장할 때
연관된 엔티티에 해당 엔티티를 수정하거나 저장해줘야 한다.
이때 지연로딩으로 설정된 연관 엔티티의 값에 접근하는 과정에서 문제가 발생하지 않았을까? 하는 생각이 들었다.

 

 

구현 코드 예시

  • PostService의 addPost 매서드

  • Post 객체를 생성할 때 지연 로딩으로 설정한 state와 user에 연관관계를 설정해야 한다.
  • 이때 state와 user는 영속상태가 아니기 때문에 Post를 생성하기 전에 각각의 repository를 통해 저장된 객체를 가져온다.
위의 코드가 비효율적이라는 생각이 들기도 하고... 프록시 객체 때문에 양방향에서 오류가 발생하면 지연로딩과 양방향 둘 중에 하나는 포기해야 하는 건가? 라는 생각이 들기도 했다.

그래서 위의 내용을 튜터님께 여쭤보면서 Entity graph라는 것에 대해 새로 알게 되었다.

 

Entity Graph

  • Repository의 매서드 위에 @EntityGraph(attributePaths = {"같이 조회할 연관된 엔티티"}를 추가
  • 지연 로딩으로 설정한 연관된 엔티티를 한번에 함께 조회 가능
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findById(Long userId);
    
    @EntityGraph(attributePaths = {"addresses"}, type = EntityGraph.EntityGraphType.LOAD)
    Optional<User> findWithAddressesById(Long userId);
}

소감

이번 팀 과제를 하면서 JPA에 대해 더 공부할 필요성을 느꼈다.
무엇보다 영속성 컨텍스트에 대해 잘 이해해야지만 앞으로 JPA를 공부할 때 수월할 것 같다는 생각이 들어서 다음에 한 번 정리해보려고 한다.