รู้ทัน Lombok: ทำไมไม่ควรใช้ @Data กับ JPA Entity และวิธีเขียนที่ถูกต้อง

February 06, 2026 · 2 min read · 1 views

Lombok เป็น Library ที่ช่วยลด Boilerplate code ได้อย่างดีเยี่ยม โดยเฉพาะ Annotation @Data ที่รวมเอา @ToString, @EqualsAndHashCode, @Getter, @Setter และ @RequiredArgsConstructor ไว้ในคำสั่งเดียว

แต่สำหรับการเขียน JPA Entity การใช้ @Data โดยไม่เข้าใจกลไกภายใน อาจนำไปสู่ปัญหาร้ายแรง 2 ประการ คือ Circular Reference (StackOverflowError) และ Performance/Logic Issues

บทความนี้จะอธิบายสาเหตุทางเทคนิคและแนวทางแก้ไขครับ

ปัญหาที่ 1: Infinite Loop จาก toString()

สาเหตุ (Cause)

@Data จะสร้าง method toString() ที่นำค่าของทุก Field ใน Class มาต่อกันเป็น String

ใน JPA เรามักมีความสัมพันธ์แบบ Bidirectional (สองทาง) เช่น Order มี OrderItem และ OrderItem ก็ผูกกลับไปหา Order

java
@Entity
@Data // <--- จุดเกิดเหตุ
public class Order {
    @OneToMany(mappedBy = "order")
    private List<OrderItem> items;
}

@Entity
@Data // <--- จุดเกิดเหตุ
public class OrderItem {
    @ManyToOne
    private Order order;
}

ผลลัพธ์ (Effect)

เมื่อมีการเรียก order.toString():

  1. Order จะเรียก items.toString()
  2. OrderItem แต่ละตัวจะเรียก order.toString()
  3. เกิดการเรียกวนซ้ำไปเรื่อยๆ จน Memory เต็ม และเกิด java.lang.StackOverflowError ทำให้ Application พังทันที

ปัญหาที่ 2: Performance และ Logic พังจาก equalsAndHashCode()

สาเหตุ (Cause)

@Data จะสร้าง equals() และ hashCode() โดยนำ ทุก Field ใน Class มาคำนวณ

ผลลัพธ์ (Effect)

1. Performance Issue (N+1 Query โดยไม่รู้ตัว) หาก Entity มีความสัมพันธ์แบบ FetchType.LAZY การที่ equals() พยายามเข้าถึง Field นั้น จะไป Trigger ให้ Hibernate ยิง Query ไปที่ Database เพื่อดึงข้อมูลจริงออกมาทันที ทั้งที่เราแค่ต้องการเช็คความเท่ากันของ Object

2. Logic Issue (Set/Map ทำงานผิดพลาด) ตามหลักการของ JPA ข้อมูลใน Entity ถือเป็น Mutable (เปลี่ยนแปลงได้) แต่ hashCode ควรจะคงที่

  • หากเรานำ Entity ไปใส่ใน HashSet หรือ HashMap
  • ต่อมามีการแก้ไขค่าบาง Field (เช่น update ชื่อ) -> ค่า HashCode เปลี่ยน
  • Collection นั้นจะหา Object ตัวเดิมไม่เจออีกเลย ทำให้ Logic ของโปรแกรมผิดเพี้ยน

วิธีแก้ไข (The Solution)

วิธีที่ดีที่สุดคือ "เลิกใช้ @Data กับ JPA Entity" และเลือกใช้ Annotation แยกตามความจำเป็นแทน ดังนี้:

1. ใช้ @Getter และ @Setter แยกกัน

เพื่อหลีกเลี่ยงการสร้าง method ที่ไม่จำเป็น

2. Override toString() และ Exclude ความสัมพันธ์

หากต้องการใช้ toString() จริงๆ ให้ใช้ @ToString.Exclude กับ Field ที่เป็นความสัมพันธ์ (Relationship) หรือเขียน toString เองโดยเลือกเฉพาะ Field ทั่วไป

3. จัดการ equals() และ hashCode() ให้ถูกหลัก JPA

สำหรับ JPA Entity การเช็คความเท่ากันควรดูที่ Primary Key (ID) เท่านั้น ไม่ใช่ดูที่ Data ทุก Field

ตัวอย่าง Code ที่ถูกต้อง (Best Practice)

java
@Entity
@Getter
@Setter
@RequiredArgsConstructor // หรือ @NoArgsConstructor ตามการใช้งาน
public class Order {
    
    @Id
    @GeneratedValue
    private Long id;
    
    private String orderNumber;

    @OneToMany(mappedBy = "order")
    @ToString.Exclude // แก้ปัญหาที่ 1: ตัดวงจร Circular Reference
    private List<OrderItem> items;

    // แก้ปัญหาที่ 2: เช็คความเท่ากันที่ ID เท่านั้น
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
        Order order = (Order) o;
        return id != null && Objects.equals(id, order.id);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

สรุป

การใช้ Lombok ไม่ใช่เรื่องผิด แต่ต้องใช้ให้ถูกที่

  • DTO / POJO: ใช้ @Data ได้เต็มที่ สะดวกและรวดเร็ว
  • JPA Entity: ห้ามใช้ @Data ให้ใช้ @Getter, @Setter และจัดการ equals/hashCode ด้วย ID เท่านั้น เพื่อป้องกันปัญหา Memory Leak และ Performance ของ Database ครับ

Responses (0)

No responses yet. Be the first to respond.