
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
관계에서 FetchType
을 EAGER로 설정하면 즉시 로딩이 일어난다.@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