jpa 계층형테이블 양방향 매핑 과정에서 무한루프, N+1문제 해결

ga-bin·2023년 12월 28일
0

jpa

목록 보기
2/2

문제상황

  • 계층형으로 구성된 테이블에서 양방향 매핑을 해 데이터를 들고와야 하는 상황이었다.
  • 이런 상황에서 무한루프가 발생했고, 원인을 찾을 수 없었다.

코드

public class Code {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long codeId;
	private String codename;
	
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "upper_code_id")
	@JsonIgnore
	private Code parentCode;
	
	@OneToMany(mappedBy = "parentCode", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
	private List<Code> childCodes;
 
	public CodeDTO toDTO() {
		Set<Long> convertedCodes = new HashSet<>();
		if (convertedCodes.contains(codeId)) {
	        return null;
	    }
		
		CodeDTO.CodeDTOBuilder builder = CodeDTO.builder();
		
		builder.codeId(codeId)
				.codename(codename);
		
		
		if(parentCode != null) {
			CodeDTO parentCodeDTO = parentCode.toDTO();
	        builder.parentCodeDTO(parentCodeDTO);
		}
		
	    if (childCodes != null && !childCodes.isEmpty()) {
            List<CodeDTO> childCodeDTOs = childCodes.stream()
                    .map(Code::toDTO)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            builder.childCodesDTO(childCodeDTOs);
        }
		
		return builder.build();
	}
 }
  • 문제가 발생한 CODE테이블은 계층형테이블이며, 자기자신을 참조하고 있다.

무한루프 해결

  • 처음에는 쿼리문을 가지고 오는 과정 자체가 문제인 줄 알았다.
  • 하지만 날라가는 쿼리문은 이것이 전부였고, 그 후에 stackoverflow가 발생해 쿼리를 가지고오는 과정에서의 문제가 아님을 짐작했다.
Hibernate: select c1_0.code_id,c1_0.codename,c1_0.upper_code_id from code c1_0 where c1_0.codename=?
Hibernate: select c1_0.code_id,c1_0.codename,c1_0.upper_code_id from code c1_0 where c1_0.code_id=?
Hibernate: select c1_0.upper_code_id,c1_0.code_id,c1_0.codename from code c1_0 where c1_0.upper_code_id=?
  • 문제가 된 부분은 다음과 같이 엔티티를 DTO로 변환하는 부분이었다.
  • 부모는 자식을 변환하고, 자식은 부모를 변환하는 일이 계속 발생하니 stackoverflow가 발생한 것이다.
  • 이를 해결하기 위해서는 자식이나 부모 둘 중 하나에서 변환과정을 끊어주면 된다.
  • 나는 다음과 같이 자식이 부모를 dto로 변환하는 부분을 주석처리해 주었다.

  • dto를 entity로 변환시키는 부분도 마찬가지로 처리해주었다.

N+1 쿼리 해결

  • 이렇게 무한루프를 해결하고 나서 부모 + 자식을 가지고오는 과정에서 n+1쿼리문이 생기는 문제가 발생했다.

  • 이는 양방향 참조관계를 설정할 때 해놓은 FetchType.LAZY때문으로, 자식이 필요할 때 쿼리를 발생시키는 방식이기 때문에 N+1참조 관계가 발생한다.

  • 하지만 그렇다고 해서 한번에 모든 객체를 다 가지고 오면 속도 저하가 생길 수도 있으므로 EAGER로 할 수도 없는 노릇이었다.

  • 결국은 부모 + 자식을 한번에 조인해서 가지고 오는 쿼리문을 작성해야했고, 다음과 같은 쿼리문으로 수정했다.

	@Query("SELECT c FROM Code c " +
		       "LEFT JOIN FETCH c.parentCode " +
		       "LEFT JOIN FETCH c.childCodes " +
		       "WHERE c.codename = :codename")
	public Code findByCodename(String codename);
  • 하지만 이렇게 작성해도 N+1 문제는 계속되었다. fetch문을 작성하는 과정에서는 join해서 쿼리문을 가지고오지만 FetchType.LAZY때문에 여전히 자식을 사용하는 과정에서 새로운 쿼리문이 발생했기 때문이다.

User
Hibernate: select c1_0.code_id,c2_0.upper_code_id,c2_0.code_id,c2_0.codename,c1_0.codename,p1_0.code_id,p1_0.codename,p1_0.upper_code_id from code c1_0 left join code p1_0 on p1_0.code_id=c1_0.upper_code_id left join code c2_0 on c1_0.code_id=c2_0.upper_code_id where c1_0.codename=?
Hibernate: select c1_0.upper_code_id,c1_0.code_id,c1_0.codename from code c1_0 where c1_0.upper_code_id=?
Hibernate: select c1_0.upper_code_id,c1_0.code_id,c1_0.codename from code c1_0 where c1_0.upper_code_id=?
Hibernate: select c1_0.upper_code_id,c1_0.code_id,c1_0.codename from code c1_0 where c1_0.upper_code_id

업로드중..

업로드중..

  • 이 사진에서 childCodes를 childCodeDTOS로 변환하는 과정에서 각각의 자식들을 따로 불러오면서 해당 문제가 발생했다.
  • 이를 해결하기 위해서 application.yml에 다음과 같이 설정했다.
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        default_batch_fetch_size: 1000
  • 이렇게 하면 in을 사용해 쿼리문을 n번이아니라 1번만 더 날려서 해결을 해준다고 한다.
  • 그 결과 다음과 같은 쿼리문이 발생했고, 계층형으로 데이터를 불러오는데 성공했다.
Hibernate: select c1_0.code_id,c2_0.upper_code_id,c2_0.code_id,c2_0.codename,c1_0.codename,p1_0.code_id,p1_0.codename,p1_0.upper_code_id from code c1_0 left join code p1_0 on p1_0.code_id=c1_0.upper_code_id left join code c2_0 on c1_0.code_id=c2_0.upper_code_id where c1_0.codename=?
Hibernate: select c1_0.upper_code_id,c1_0.code_id,c1_0.codename from code c1_0 where c1_0.upper_code_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?.....

0개의 댓글