본문 바로가기

JAVA/JPA & Querydsl

상속 관계 Entity에서 자식 Entity 조회하기 with HibernateProxy

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="setting")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Setting {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id")
    private Long id;
    
    ...
    
}


@Entity
@Table(name = "item_setting")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class ItemSetting extends Setting {

    @Column(name = "content", columnDefinition = "longtext")
    private String content;

    ...
}

하나의 엔티티를 상속 확장해서 사용하는 경우가 있었습니다.

 

이때 Setting 엔티티를 조회해서 ItemSetting으로  Class casting 해서 자식 엔티티를 사용하려고 했을때 오류가 발생했습니다.

EXCEPTION JAVA.LANG.CLASSCASTEXCEPTION: ORG.HIBERNATE.MAPPING.JOINEDSUBCLASS CANNOT BE CAST TO ORG.HIBERNATE.MAPPING.ROOTCLASS

 처음 조회한 엔티티는 Setting 엔티티이고 Setting 타입으로 원본 엔티티 인스턴스가 생성 되었고, 다운 캐스팅 했을때 프록시 클래스는 원본 엔티티를 참조하므로 자식 타입으로 캐스팅할 수 없게 됩니다. 

이처럼 엔티티를 상속 확장해서 부모 - 자식 관계에 있는 엔티티의 경우 프록시를 부모타입으로 조회하면 문제가 발생합니다.

 

상속 관계에서 프록시 문제를 해결하는 방법이 있을까요?

 

1. 쿼리로 대상 직접 조회

부모 타입을 먼저 조회하지말고 직접 조인해서 자식 타입을 조회해서 해결할 수 있습니다.

하지만 다형성을 활용 할 수 는 없습니다.

 

2. 프록시 벗기기

HibernateProxy에서 직접 원본 엔티티를 찾는 기능을 사용합니다.

public interface HibernateProxy extends Serializable, PrimeAmongSecondarySupertypes {

	/**
	 * Extract the LazyInitializer from the object, if
	 * and only if the object is actually an HibernateProxy.
	 * If not, null is returned.
	 * @param object any entity
	 * @return either null (if object is not an HibernateProxy) or the LazyInitializer of the HibernateProxy.
	 */
	static LazyInitializer extractLazyInitializer(final Object object) {
		if ( object instanceof PrimeAmongSecondarySupertypes ) {
			PrimeAmongSecondarySupertypes t = (PrimeAmongSecondarySupertypes) object;
			final HibernateProxy hibernateProxy = t.asHibernateProxy();
			if ( hibernateProxy != null ) {
				return hibernateProxy.getHibernateLazyInitializer();
			}
		}
		return null;
	}

	/**
	 * Perform serialization-time write-replacement of this proxy.
	 *
	 * @return The serializable proxy replacement.
	 */
	Object writeReplace();

	/**
	 * Get the underlying lazy initialization handler.
	 *
	 * @return The lazy initializer.
	 */
	LazyInitializer getHibernateLazyInitializer();

	/**
	 * Special internal contract to optimize type checking
	 * @see PrimeAmongSecondarySupertypes
	 * @return this same instance
	 */
	@Override
	default HibernateProxy asHibernateProxy() {
		return this;
	}
}

 

extractLazyInitializer static 메소드를 활용합니다.

 

public Setting getEffectiveDeliverySettings(final User candidate, final Delivery delivery) {
        
        Setting result = delivery.getSetting();
        if (result==null) {
            result = createDefaultDeliverySettings(candidate, delivery.getAssessment().getAssessmentType());
        } else {
            result = (Setting) HibernateProxy.extractLazyInitializer(result).getImplementation();
        }
        return result;
    }

 

 이렇게 가져온 엔티티는 프록시에서 원본 엔티티를 가져올수 있어, 자식 엔티티로 다운캐스팅 하더라도 오류없이 가져올 수 있었습니다.

 

Reference

https://www.nowwatersblog.com/jpa/ch15/15-3