n:n 관계 형변환 오류

트러블 슈팅
김인범's avatar
Jan 18, 2025
n:n 관계 형변환 오류
notion image
PersistentBag을 AccountDTO로 캐스팅하려고 시도하면서 발생한 ClassCastException로 PersistentBag은 Hibernate가 컬렉션을 지연 로딩할 때 사용하는 클래스이다. CreditDTO의 생성자가 이를 직접 처리하려고 해서 문제가 발생한 것이다!

→ 해결방법 : 강제 형변환을 안하면 된다!

→ 또는 FetchType을 Lazy가 아닌 Eager로 변경해도 된다.
Hibernate.initialize() 메서드를 사용하여 Lazy Loading 강제 로딩
 

1. 문제 원인

Hibernate에서 PersistentBag지연 로딩(Lazy Loading) 시 컬렉션을 프록시로 감싸서 관리하는 클래스다. 여기에서 PersistentBag과 직접적인 클래스(DTO)를 서로 형변환하려고 시도하여 ClassCastException이 발생했다.
 
발생한 문제 원인의 원인
  • n:n 관계1:n 관계에서 컬렉션 데이터를 로딩할 때, Hibernate는 Lazy Loading을 기본으로 사용하는데 이 때 실제 컬렉션이 아닌 프록시 객체인 PersistentBag 을 반환한다.
  • DTO로 변환하거나 캐스팅하려고 하면 프록시 객체(PersistentBag)와 실제 DTO 클래스 간에 타입이 다르므로 ClassCastException이 발생한다.

ClassCastException 이해하기

PersistentBag은 java.util.List를 구현하고 있지만, 기본적으로 Hibernate의 프록시 클래스로 PersistentBag를 강제로 DTO나 다른 컬렉션 타입으로 변환하려고 하면 ClassCastException오류가 발생한다.
예)
@Entity public class User { @ManyToMany private List<Account> accounts; }
accounts를 DTO로 변환할 때:
UserResponse response = new UserResponse(); response.setAccounts((List<AccountDTO>) user.getAccounts()); // ClassCastException 발생
  • user.getAccounts()는 Hibernate가 반환한 PersistentBag이라서 List<AccountDTO>로 캐스팅하려고 하면 타입 불일치로 예외가 발생한다.

2. 해결 방법

방법 1: 강제 형변환을 피하기

Hibernate의 컬렉션 프록시인 PersistentBag을 직접 변환하지 말고, DTO로 변환하는 메서드를 따로 작성한다
List<AccountDTO> accountDTOs = user.getAccounts() .stream() .map(account -> new AccountDTO(account.getId(), account.getName())) .collect(Collectors.toList()); //Stream을 이용해 PersistentBag의 내부 원소를 순회하면서 DTO로 변환한다.

방법 2: FetchType을 EAGER로 변경

@ManyToMany 또는 @OneToMany 관계에서 FetchTypeEAGER로 설정하면 즉시 로딩이 일어난다.
@ManyToMany(fetch = FetchType.EAGER) private List<Account> accounts;
주의사항:
  • EAGER로 설정하면 데이터베이스에서 연관된 엔티티를 즉시 로딩하기 때문에 성능 이슈가 발생할 수 있다.
  • 특히 n:n 관계에서 너무 많은 데이터를 한 번에 로드하면 성능 저하의 원인이 된다.

방법 3: Hibernate 초기화 (Lazy Loading 강제 로딩)

PersistentBag이 Lazy 상태일 경우, 강제로 초기화할 수 있습니다.
Hibernate의 Hibernate.initialize() 메서드를 사용하면 Lazy Loading 컬렉션을 강제로 로딩할 수 있다.
Hibernate.initialize(user.getAccounts()); List<AccountDTO> accountDTOs = user.getAccounts() .stream() .map(account -> new AccountDTO(account.getId(), account.getName())) .collect(Collectors.toList());
장점: Lazy 로딩을 유지하면서 필요할 때만 데이터를 로딩한다.
 
 
Share article

taker