강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

papakang22님의 프로필 이미지
papakang22

작성한 질문수

Hibernate Proxy객체가 실제객체로 변환이 안돼요

작성

·

21

·

수정됨

0

먼저 Entity Code부터 보여드리겠습니다.

 

FieldGroupMapping(부모)


@Entity
@Table(name = "FIELD_GROUP_MAPPING")
@Getter
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FieldGroupMapping extends CreationAuditingEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "FIELD_GRP_MPPG_ID", nullable = false)
  private Long id;

  @Column(name = "FIELD_CD", length = 50, nullable = false)
  private String fieldCd;

  @ManyToOne(fetch = FetchType.EAGER, optional = false)
  @JoinColumn(name = "FIELD_CD",
      referencedColumnName = "FIELD_CD",
      insertable = false,
      updatable = false,
      foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
  private FieldMetadata field;
}

 

FieldMetadata (자식)


@Entity
@Table(name = "FIELD_METADATA")
@Getter
@SuperBuilder
@AllArgsConstructor
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "FIELD_TYPE")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FieldMetadata extends CreationAuditingEntity {

  @Id
  @Column(name = "FIELD_CD", length = 50, nullable = false)
  private String fieldCd;

  @Column(name = "LABEL", length = 50, nullable = false)
  private String label;

  @Enumerated(EnumType.STRING)
  @Column(name = "DATA_TYPE", columnDefinition = "varchar(20)", nullable = false)
  private FieldDataType dataType;

}

 

RealFieldMetadata (상속된 자식)


@Entity
@Table(name = "REAL_FIELD_METADATA")
@Getter
@SuperBuilder
@AllArgsConstructor
@DiscriminatorValue(FieldDiscriminatorValues.D_REAL)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@PrimaryKeyJoinColumn(name = "FIELD_CD")
public class RealFieldMetadata extends FieldMetadata {

  @Enumerated(EnumType.STRING)
  @Column(name = "TABLE_ALIAS", columnDefinition = "varchar(50)", nullable = false)
  private TableAlias tableAlias;

  @Column(name = "COLUMN_NAME", nullable = false, length = 50)
  private String columnName;

  @Builder.Default
  @Convert(converter = BooleanYNConverter.class)
  @Column(name = "REQUIRES_JOIN", nullable = false, columnDefinition = "char(1) default 'N'")
  private boolean requiresJoin = false;

  @Enumerated(EnumType.STRING)
  @Column(name = "JOIN_TABLE", columnDefinition = "varchar(50)")
  private TableAlias joinTable;


}


FieldGroupMapping 과 FieldMetadata는 1:N 관계입니다.

List<String> idList = List.of("GD01", "GD02", "GD03");
// fetch join 없이 조회 
List<FieldGroupMapping> fieldGroupMappings1 = fieldGroupRepository.findAllById(idList);

Long gridId = 1L;
// fetch join으로 조회
List<FieldGroupMapping> fieldGroupMappings2 = gridRepository.getGrid(gridId)
    .map(Grid::getFieldGroupMapping)
    .toList();

for (FieldGroupMapping mapping1 : fieldGroupMappings1) {
    FieldMetadata field = mapping1.getField();
    // 여기서 BreakPoint로 field를 보면 Proxy 객체
}

for (FieldGroupMapping mapping2 : fieldGroupMappings2) {
    FieldMetadata field = mapping2.getField()
    // 여기서 BreakPoint로 field를 보면 실제 객체 (Unproxy상태)
}

 

위 상황에서 첫번째 fieldGroupMappings1을 조회할땐 fetch join없이 hibernate로만 조회합니다.

두번째 fieldGroupMappings2를 조회할땐 Grid -> FieldGroupMapping -> field 연관관계 모두 fetch join을 써서 조회합니다.

 

제 지식으로는 두개 for문에서 모두 field는 Unproxy상태여야합니다.
단, fetch join이 없는 첫번째 리스트에서는 Select Query가 나가겠죠.

하지만, 테스트 결과는 첫번째 리스트에서의 field는 계속 Proxy상태로 되어있어. field 내부의 필드들이 null로 되어있었습니다.

 

또, 제 어플리케이션의 설정으로 default batch size는 1000으로 되어있어. select문의 조건에 in절로 채워져서 쿼리가 1번 발생하였습니다.

 

제 지식으로는 Select쿼리가 발생하였으므로, 첫번째 리스트도 field는 실제 객체여야하는데 왜 프록시객체로 남아있을까요? FieldMetadata 엔티티가 상속Entity여서 발생하는 문제일까요?

정답을 알려주세요 ㅠㅠㅠ

 

 

 

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다.
papakang22님, 설명하신 문제를 바탕으로 Hibernate에서 발생할 수 있는 프록시 관련 이슈를 살펴보겠습니다.

첫 번째로, fetch join이 없는 경우에는 Hibernate가 연관된 객체를 필요 시점(즉, 실제 데이터 접근 시점)에 lazy loading을 통해 로딩합니다. 이때, FieldMetadata@ManyToOne 관계이므로 기본적으로 LAZY 로딩 전략을 사용합니다. 따라서 mapping1.getField() 호출 시 FieldMetadata의 실제 객체가 아닌 프록시 객체로 반환될 수 있습니다.

반면, 두 번째 케이스에서는 fetch join을 사용하여 쿼리 단계에서 필요한 데이터를 모두 가져오기 때문에 결과적으로 프록시 객체가 아닌 실제 객체로 로드됩니다.

두 경우 간의 차이는 @ManyToOne(fetch = FetchType.EAGER) 설정에 기인할 수 있습니다. EAGER 로딩 설정에도 불구하고 정상적으로 로딩되지 않는 경우가 발생할 수 있으므로, 다음을 확인해 보시는 것이 좋습니다:

  1. EntityManager Configuration: FetchType.EAGER로 설정했음에도 프록시 객체로 반환되는데는 여러 설정 및 환경적 요인이 있을 수 있으니, EntityManager 설정 및 환경을 확인해 보세요.

  2. Inheritance 전략: 상속 구조(@Inheritance(strategy = InheritanceType.JOINED))로 인해 문제 발생 가능성이 있을 수 있습니다. 이런 상황에서는 해당 객체의 타입 캐스팅 및 초기화 과정에서 문제가 발생할 수 있으므로 상속 구조 내의 설정을 확인해 보세요.

  3. Hibernate Initialization 호출: 명시적으로 초기화할 수 있도록 Hibernate.initialize(object)를 통해 프록시 객체를 초기화하는 것도 한 가지 방법입니다.

  4. Hibernate 버그 확인: 사용중인 Hibernate 버전에서 관련된 알려진 버그가 없는지 공식 문서를 통해 확인이 필요합니다.

아래의 Spring Data JPA 공식 문서도 도움이 될 수 있으니 참고 바랍니다:

위 방법으로 해결되지 않는다면, 환경 설정 상태를 좀 더 세부적으로 점검하시거나, 소규모 프로젝트로 테스트해보는 것도 좋은 방법일 수 있습니다. 추가적인 문제가 있으면 다시 질문 주시면 감사하겠습니다.

papakang22님의 프로필 이미지
papakang22

작성한 질문수

질문하기