avatar
띵로그

Lombok의 @EqualsAndHashCode, 당신의 JPA 성능을 망칠 수도 있똬

편리하지만 치명적인 함정: Lombok의 @EqualsAndHashCode 사용 시 JPA 엔티티 성능 문제를 피하는 방법
#롬복JPAJavaEqualsHashCodeSpringBoot성능최적화지연로딩개발팁백엔드ORM
a month ago
·
10 min read

2116

분명 Lombok의 애너테이션을 사용하는 것은 편리한 코드 자동 생성을 제공하지만, 성능 및 메모리 사용 측면에서 예상치 못한 문제가 발생할 수 있다는 것을 알고 계신가요???? (아니면 아직 경험을 못 해봤거나..)

2107

처음 Lombok을 도입했을 때, 코드의 양을 줄여주고, 수동으로 일일히 보일러 플레이트 코드를 작성할 필요 없이 간단하게 해결되는 점이 너무 편리하고 좋았습니다. 물론 지금도 좋아하구요 👍👍

하.지.만 실제 프로젝트에서 여러 연관 관계를 가진 엔티티들을 다루다 보니, 예상치 못한 성능 저하나 무한 재귀 호출 등의 문제가 발생하기 시작했습니다.

특히 Lazy 로딩 필드에서 의도치 않게 데이터베이스 조회가 일어나는 경험을 하면서 무조건적으로 롬복 어노테이션을 사용하는 것이 위험할 수 있음을 깨닫게 되었죠..

2108

이 글에서는 롬복의 여러 어노테이션 중 특히 @EqualsAndHashCode 사용 시 발생할 수 있는 주요 문제점들과 이를 해결하기 위한 대안에 대해 알아보겠습니다


1. @EqualsAndHashCode의 문제점

@EqualsAndHashCode는 분명 편리한 자동 코드 생성을 가능하게 하지만, JPA와 결합할 때 몇 가지 주요 문제점들이 발생합니다. 이를 아래에서 자세히 살펴보겠습니다.

1.1 Lazy 로딩 및 연관 관계 문제

@EqualsAndHashCode는 모든 필드를 기반으로 equalshashCode 메서드를 자동으로 생성합니다.

하지만 JPA 엔티티에서 연관 관계가 있는 필드를 사용할 경우, 예상치 못한 Lazy 로딩이 발생하여 성능에 큰 영향을 줄 수 있습니다.

문제 설명

JPA 엔티티에서 연관 관계를 가지는 필드를 @ManyToOne이나 @OneToMany로 선언할 때, 이를 Lazy 로딩 전략으로 설정하는 것이 일반적입니다

그러나 @EqualsAndHashCode 애너테이션이 연관된 필드를 참조할 경우, 해당 필드가 아직 초기화되지 않았다면 데이터베이스 조회가 발생합니다.

2109

이는 의도치 않은 SQL 쿼리를 유발하고, 성능 저하의 원인이 될 수 있습니다.

예시 코드

@EqualsAndHashCode
@Entity
public class Order {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;
    
}

위 예시에서 Order 클래스의 customer 필드는 Lazy 로딩됩니다.

그러나 @EqualsAndHashCode가 적용되면 equalshashCode 메서드 호출 시 customer 필드를 참조하게 되어 의도치 않은 데이터베이스 조회가 발생할 수 있습니다.

1.2 StackOverflowError 발생 위험

양방향 연관 관계에 있는 엔티티들에 @EqualsAndHashCode를 사용하면, 무한 재귀 호출로 인해 StackOverflowError가 발생할 수 있습니다.

문제 설명

양방향 연관 관계가 설정된 엔티티들은 서로를 참조하게 됩니다. 이때 @EqualsAndHashCode가 적용된 필드에서 서로를 계속 참조하는 구조가 되어, 무한 재귀 호출이 발생합니다.

이로 인해 애플리케이션은 StackOverflowError로 인해 비정상 종료될 수 있습니다.

예시 코드

@EqualsAndHashCode
@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "parent")
    private List<Child> children;
    
}

@EqualsAndHashCode
@Entity
public class Child {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    private Parent parent;

}

위 코드에서 ParentChild는 서로 양방향 연관 관계를 가지고 있으며, 양쪽에 @EqualsAndHashCode가 적용되어 있다면 equalshashCode 메서드에서 무한 재귀가 발생합니다.

2110

이는 결과적으로 StackOverflowError를 유발하게 됩니다.

1.3 메모리 과다 사용 문제

모든 필드를 사용하여 hashCode를 생성하는 경우, 엔티티의 크기가 크다면 메모리 사용량이 증가하게 됩니다. 이는 특히 대규모 엔티티나 복잡한 객체 구조를 가진 경우 문제를 일으킬 수 있습니다.

문제 설명

JPA에서는 엔티티의 hashCodeequals 메서드가 영속성 컨텍스트에서 객체를 비교하고 식별하는 데 중요한 역할을 합니다.

모든 필드를 hashCode 생성에 사용하면 필드의 데이터 크기에 비례하여 메모리 사용량이 급격히 증가하게 됩니다.

예시 코드

@EqualsAndHashCode
@Entity
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String description;
    private BigDecimal price;
    private LocalDateTime createdAt;
    
    ...

}

위 코드에서 모든 필드를 사용하여 hashCode를 생성하는 경우, Product 객체가 크다면 메모리 과다 사용 및 성능 저하를 초래할 수 있습니다.

2111

이는 특히 해당 객체들이 컬렉션에 포함되어 있을 때 문제를 더욱 심화시킵니다.


2. 대안 및 해결 방안

@EqualsAndHashCode 사용 시 발생할 수 있는 위와 같은 문제를 해결하기 위해, 다음과 같은 대안을 고려할 수 있습니다.

2.1 식별자 필드만 사용

@EqualsAndHashCode 대신, 엔티티의 고유 식별자 필드(예: id)만을 기준으로 equalshashCode 메서드를 작성하는 것이 좋습니다.

이를 통해 불필요한 필드 참조로 인한 성능 저하를 방지할 수 있습니다.

예시 코드

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Product {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include
    private Long id;

    private String name;
    private String description;
}

2.2 Lombok의 @EqualsAndHashCode 옵션 활용

Lombok의 @EqualsAndHashCode 애너테이션에 onlyExplicitlyIncluded = true 옵션을 사용하고, 필요한 필드에 @EqualsAndHashCode.Include를 적용하는 방식도 추천됩니다.

2112

이를 통해 특정 필드만 포함하여 equalshashCode를 정의할 수 있습니다.

예시 코드

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Order {

    @Id
    @GeneratedValue
    @EqualsAndHashCode.Include
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;
    
}

위 코드에서는 onlyExplicitlyIncluded = true를 사용하여 id 필드만 equalshashCode에 포함시킵니다.

이를 통해 Lazy 로딩 필드를 참조하는 문제나 불필요한 메모리 사용 문제를 피할 수 있습니다.


3. 결론

JPA 엔티티에서 Lombok의 @EqualsAndHashCode 애너테이션을 사용할 때는 Lazy 로딩 필드 참조, 양방향 연관 관계로 인한 StackOverflowError, 메모리 과다 사용 등의 문제에 주의해야 합니다.

이러한 문제를 방지하기 위해 식별자 필드만 사용하거나, Lombok의 옵션을 적절히 활용하여 불필요한 필드를 제외하는 것이 바람직합니다.

애플리케이션의 성능과 안정성을 보장하려면, @EqualsAndHashCode의 사용을 신중하게 검토하고 필요한 경우 커스텀 메서드를 작성하는 것을 고려하는 것이 좋습니다.

2115


- 컬렉션 아티클






주니어 백엔드 개발자입니다 :)