기록

[자바 ORM 표준 JPA 프로그래밍] JPA 컬렉션과 부가 기능: 컬렉션, 컨버터, 리스너, 엔티티 그래프(14장) 본문

교육/책

[자바 ORM 표준 JPA 프로그래밍] JPA 컬렉션과 부가 기능: 컬렉션, 컨버터, 리스너, 엔티티 그래프(14장)

youngyin 2024. 12. 14. 10:00

시작하면서

JPA는 객체 지향 프로그래밍의 장점을 살려 데이터베이스를 효율적으로 관리할 수 있도록 도와줍니다. 특히 컬렉션, 컨버터, 리스너, 엔티티 그래프와 같은 부가 기능은 코드의 생산성과 유지보수를 크게 향상시킵니다. 이 글에서는 이러한 기능을 이해하기 쉽게 정리하고, 실제 코드와 함께 활용법을 살펴보겠습니다.


1. 컬렉션

컬렉션은 엔티티와 연관된 데이터를 저장하는 데 사용됩니다. JPA는 Collection, List, Set, Map 등 다양한 컬렉션 타입을 지원하며, 각 타입은 고유한 특징을 가지고 있습니다.

1.1 CollectionList

  • 중복 데이터를 허용합니다.
  • 엔티티를 추가해도 지연 로딩된 컬렉션은 초기화되지 않습니다.

코드 예제

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

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

1.2 Set

  • 중복을 허용하지 않습니다.
  • 엔티티 추가 시 중복 여부를 체크하며, 지연 로딩된 컬렉션을 초기화합니다.

코드 예제

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private Set<Member> members = new HashSet<>();
}

1.3 @OrderColumn

  • 데이터베이스에 컬렉션의 순서를 저장합니다.
  • 순서를 관리하는 별도의 컬럼(list_order)이 추가됩니다.
  • 순서가 변경되면 업데이트 쿼리가 발생합니다.

코드 예제

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

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

 

@OrderColumn을 사용하면 다음과 같이 순서를 저장하는 컬럼이 추가됩니다.

1.4 @OrderBy

  • 특징:
    • 데이터베이스의 ORDER BY 절을 사용하여 컬렉션을 정렬합니다.
    • 추가적인 컬럼이 필요하지 않으며, 데이터베이스 조회 시 정렬만 적용됩니다.

코드 예제

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

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

2. 컨버터 (@Converter)

컨버터는 엔티티의 데이터를 변환하여 데이터베이스에 저장하거나 조회할 때 변환하는 역할을 합니다.

예제: Boolean 값을 Y/N으로 변환

@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return (attribute != null && attribute) ? "Y" : "N";
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return "Y".equals(dbData);
    }
}

컨버터 적용

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

    @Convert(converter = BooleanToYNConverter.class)
    private Boolean isActive;
}

 

Boolean 값을 Y/N으로 저장하는 방식은 일반적인가?

Boolean 값을 "Y" 또는 "N" 형태로 변환하여 데이터베이스에 저장하는 방법은 전통적인 방식에서 흔히 사용되었습니다. 특히 레거시 시스템이나 과거의 데이터베이스 설계에서는 문자 기반의 컬럼이 선호되곤 했습니다.

현대적인 아키텍처에서는 Boolean 값을 데이터베이스에 "Y/N"으로 저장하는 방식이 일반적이지 않습니다. 대부분의 현대 데이터베이스는 Boolean 또는 BIT 타입을 지원하며, 이를 그대로 사용하는 것이 효율적입니다. Boolean 타입은 저장 공간을 덜 차지하고, 추가 변환 없이 코드와 데이터베이스 간에 일관성을 유지할 수 있습니다.

그러나 레거시 시스템과의 호환성 또는 특정 표준 요구사항이 있는 경우 "Y/N" 방식을 사용할 수 있으며, 이 경우 JPA의 @Converter를 활용해 변환 로직을 자동화하는 것이 좋습니다.


3. 리스너

리스너는 엔티티의 생명주기 이벤트를 활용하여 공통 로직을 처리하거나 특정 이벤트 시 작업을 자동화하는 데 사용됩니다.

3.1 주요 이벤트

이벤트 발생 시점 활용 사례
@PrePersist 엔티티가 저장되기 전 데이터 초기화, 감사 로그 추가
@PostPersist 엔티티가 저장된 후 저장 후 알림 또는 로그 기록
@PreUpdate 엔티티가 업데이트되기 전 데이터 유효성 검증
@PostUpdate 엔티티가 업데이트된 후 변경 내역 추적
@PreRemove 엔티티가 삭제되기 전 연관 데이터 삭제 처리
@PostRemove 엔티티가 삭제된 후 삭제 알림
@PostLoad 엔티티가 로딩된 후 추가 데이터 로드 또는 가공

3.2 리스너 활용 사례

(1) 엔티티 내부에 이벤트 정의

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @PrePersist
    public void beforeSave() {
        System.out.println("Member is about to be saved: " + name);
    }

    @PostPersist
    public void afterSave() {
        System.out.println("Member has been saved: " + name);
    }
}

(2) 외부 리스너로 공통 로직 분리

@Entity
@EntityListeners(AuditListener.class)
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

public class AuditListener {
    @PrePersist
    public void logCreation(Object entity) {
        System.out.println("Creating: " + entity);
    }

    @PreUpdate
    public void logUpdate(Object entity) {
        System.out.println("Updating: " + entity);
    }
}

4. 엔티티 그래프

엔티티 그래프는 Lazy 로딩 환경에서 연관 데이터를 효율적으로 조회할 수 있도록 설계된 기능입니다.

4.1 Named 엔티티 그래프 정의

@Entity
@NamedEntityGraph(
    name = "Team.withMembers",
    attributeNodes = @NamedAttributeNode("members")
)
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

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

4.2 엔티티 그래프 사용

EntityGraph<?> graph = em.getEntityGraph("Team.withMembers");
Map<String, Object> hints = new HashMap<>();
hints.put("javax.persistence.fetchgraph", graph);

Team team = em.find(Team.class, 1L, hints);

5. 정리

  • 컬렉션은 데이터의 중복, 순서, 정렬 등 특성에 맞게 적절한 타입을 선택할수 있습니다.
  • 컨버터는 데이터를 저장하거나 조회할 때 변환 로직이 필요한 경우 유용합니다.
  • 리스너는 엔티티 생명주기 이벤트를 활용하여 공통 로직을 분리하고 자동화할 수 있습니다.
  • 엔티티 그래프는 특정 시점에서 연관 데이터를 효율적으로 조회해야 할 때 유용합니다.
Comments