JPA 상속 @EntityGraph는 서브 클래스의 선택적 연관을 포함합니다.


12

다음 도메인 모델을 감안할 때 s 및 해당 하위 하위를 Answer포함한 모든을로드 하고 JSON으로 변환하려고합니다. 작동하는 솔루션이 있지만 ad-hoc을 사용하여 제거하려는 N + 1 문제가 발생합니다 . 모든 연결이 구성되었습니다 .ValueAnswerDTO@EntityGraphLAZY

여기에 이미지 설명을 입력하십시오

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

방법 @EntityGraph에 ad-hoc 을 사용하면 연결 Repository에서 N + 1을 방지하기 위해 값을 프리 페치 할 수 Answer->Value있습니다. 내 결과는 괜찮지 만 s 의 selected연결이 느리게로드되어 또 다른 N + 1 문제가 MCValue있습니다.

이것을 사용하여

@EntityGraph(attributePaths = {"value.selected"})

selected필드는 물론 일부 Value엔티티 의 일부 이므로 실패합니다 .

Unable to locate Attribute  with the the given name [selected] on this ManagedType [x.model.Value];

JPA selected가 값이 MCValue? 인 경우 연결을 가져 오도록 시도하는 방법은 무엇입니까? 와 같은 것이 필요합니다 optionalAttributePaths.

답변:


8

EntityGraph연관 속성이 수퍼 클래스의 일부이고 모든 서브 클래스의 일부인 경우 에만 사용할 수 있습니다 . 그렇지 않으면, 현재 얻는 것과 EntityGraph항상 실패합니다 Exception.

N + 1 선택 문제를 피하는 가장 좋은 방법은 쿼리를 두 개의 쿼리로 나누는 것입니다.

첫 번째 쿼리 는 속성 을 MCValue사용하여 EntityGraph매핑 된 연결을 가져 오기 위해를 사용 하여 엔터티를 가져옵니다 selected. 해당 쿼리 후에이 엔티티는 Hibernate의 1 단계 캐시 / 지속성 컨텍스트에 저장됩니다. Hibernate는 2 번째 질의 결과를 처리 할 때 그것들을 사용할 것이다.

@Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
@EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();

그런 다음 두 번째 쿼리는 Answer엔터티 를 가져오고 를 사용 EntityGraph하여 연결된 Value엔터티 도 가져옵니다 . 각 Value엔티티에 대해 Hibernate는 특정 서브 클래스를 인스턴스화하고 1 단계 캐시에 이미 해당 클래스 및 기본 키 조합에 대한 오브젝트가 포함되어 있는지 확인합니다. 이 경우, Hibernate는 질의에 의해 반환 된 데이터 대신에 1 단계 캐시의 객체를 사용합니다.

@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();

MCValue관련 엔터티가있는 모든 엔터티를 이미 가져 왔으므로 selected이제 Answer초기화 된 value연결 이있는 엔터티를 가져옵니다 . 연결에 MCValue엔터티 가 포함 된 경우 연결 selected도 초기화됩니다.


두 개의 쿼리, 즉 답변 + 값을 가져 오는 첫 번째 쿼리와가있는 답변을 가져 오는 두 번째 쿼리가 있다고 생각 selected했습니다 MCValue. 나는 이것이 추가적인 루프를 요구한다는 것을 싫어했고 데이터 세트 간의 매핑을 관리해야합니다. 나는 이것을 위해 Hibernate 캐시를 이용하는 아이디어를 좋아한다. 결과를 포함하기 위해 캐시에 의존하는 것이 얼마나 안전한지 (일관성 측면에서) 자세히 설명 할 수 있습니까? 트랜잭션에서 쿼리를 수행 할 때 작동합니까? 나는 발견하기 어려운 산발적 인 초기화 오류를 두려워합니다.
스턱

1
동일한 트랜잭션 내에서 두 쿼리를 모두 수행해야합니다. 그렇게하고 지속성 컨텍스트를 지우지 않는 한 절대 안전합니다. 1 차 캐시에는 항상 MCValue엔티티가 포함 됩니다. 그리고 추가 루프가 필요하지 않습니다. 에 MCValue결합하고 Answer현재 조회와 동일한 WHERE 절을 사용하는 하나 의 조회로 모든 엔티티를 페치해야 합니다. 나는 또한 오늘의 라이브 스트림에서 이것에 대해 이야기 했습니다 : youtu.be/70B9znTmi00?t=238 3:58에 시작했지만 그 사이에 몇 가지 다른 질문이 있습니다 ...
Thorben Janssen

감사합니다. 감사합니다. 또한이 솔루션에는 서브 클래스 당 1 개의 쿼리가 필요하다는 것을 추가하고 싶습니다. 따라서 유지 관리가 쉽지만이 솔루션이 모든 경우에 적합한 것은 아닙니다.
스턱

마지막 주석을 약간 수정해야합니다. 물론 문제가있는 서브 클래스마다 쿼리 만 필요합니다. 또한 서브 클래스의 속성에 대해서는을 사용하기 때문에 문제가되지 않는 것 같습니다 SINGLE_TABLE_INHERITANCE.
스턱

7

Spring-Data가 무엇을하고 있는지 알지 못하지만 그렇게하려면 일반적으로 TREAT하위 연결에 액세스 할 수 있도록 연산자를 사용해야 하지만 해당 연산자에 대한 구현은 매우 버그가 있습니다. Hibernate는 여기에 필요한 암시 적 하위 유형 속성 액세스를 지원하지만 분명히 Spring-Data는 이것을 올바르게 처리 할 수 ​​없습니다. JPA에서 작동하는 라이브러리 인 Blaze-Persistence Entity-Views를 살펴보면 임의의 구조를 엔티티 모델에 매핑 할 수 있습니다. DTO 모델을 안전한 형식과 상속 구조로 매핑 할 수 있습니다. 사용 사례에 대한 엔티티보기는 다음과 같습니다.

@EntityView(Answer.class)
interface AnswerDTO {
  @IdMapping
  Long getId();
  ValueDTO getValue();
}
@EntityView(Value.class)
@EntityViewInheritance
interface ValueDTO {
  @IdMapping
  Long getId();
}
@EntityView(TextValue.class)
interface TextValueDTO extends ValueDTO {
  String getText();
}
@EntityView(RatingValue.class)
interface RatingValueDTO extends ValueDTO {
  int getRating();
}
@EntityView(MCValue.class)
interface TextValueDTO extends ValueDTO {
  @Mapping("selected.id")
  Set<Long> getOption();
}

Blaze-Persistence에서 제공하는 스프링 데이터 통합을 통해 이와 같은 저장소를 정의하고 결과를 직접 사용할 수 있습니다

@Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
  List<AnswerDTO> findAll();
}

AnswerDTO다음과 같이 매핑 한 것을 선택하는 HQL 쿼리를 생성합니다 .

SELECT
  a.id, 
  v.id,
  TYPE(v), 
  CASE WHEN TYPE(v) = TextValue THEN v.text END,
  CASE WHEN TYPE(v) = RatingValue THEN v.rating END,
  CASE WHEN TYPE(v) = MCValue THEN s.id END
FROM Answer a
LEFT JOIN a.value v
LEFT JOIN v.selected s

흠, 내가 이미 찾은 라이브러리에 대한 힌트에 감사드립니다.하지만 두 가지 주요 이유로 사용하지 않을 것입니다. 처음부터). 2) 단일 쿼리를 최적화하기 위해 더 복잡한 기술 스택을 사용하지 않습니다. (당신의 lib는 더 많은 것을 할 수 있지만, 우리는 일반적인 기술 스택을 선호하고 JPA 솔루션이 없다면 사용자 정의 쿼리 / 변환을 구현하기를 원합니다).
스턱

1
Blaze-Persistence는 오픈 소스이며 Entity-Views는 표준 JPQL / HQL을 기반으로 구현됩니다. 구현하는 기능은 안정적이며 표준 버전에서 작동하기 때문에 이후 버전의 Hibernate에서 계속 작동합니다. 단일 유스 케이스로 인해 무언가를 소개하고 싶지 않다는 것을 알고 있지만 엔티티 뷰를 사용할 수있는 유일한 유스 케이스는 아닌 것 같습니다. 엔터티 뷰를 도입하면 일반적으로 상용구 코드의 양이 크게 줄어들고 쿼리 성능이 향상됩니다. 도움이되는 도구를 사용하고 싶지 않다면 그렇게하십시오.
Christian Beikov

최소한 문제를 이해하지 못하고 해결책을 제시하십시오. 따라서 답변이 원래 문제에서 어떤 일이 벌어지고 있는지, JPA가 어떻게 해결할 수 있는지 설명하지 않아도 현상금을 얻습니다. 내 인식으로는 JPA에서 지원하지 않으며 기능 요청이되어야합니다. JPA만을 대상으로 한보다 정교한 답변을 위해 또 다른 현상금을 제공 할 것입니다.
스턱

JPA로는 불가능합니다. JPA 제공자에서 완전히 지원되지 않거나 EntityGraph 주석에서 지원되지 않는 TREAT 연산자가 필요합니다. 따라서이를 모델링 할 수있는 유일한 방법은 명시 적 조인을 사용해야하는 Hibernate 암시 적 하위 유형 특성 분석 기능을 통하는 것입니다.
Christian Beikov

1
당신의 대답에서 뷰 정의가 있어야합니다interface MCValueDTO extends ValueDTO { @Mapping("selected.id") Set<Long> getOption(); }
스턱

0

내 최신 프로젝트는 GraphQL (처음으로 사용)을 사용했으며 N + 1 쿼리에 큰 문제가 있었고 필요할 때 테이블에 대해서만 조인하도록 쿼리를 최적화하려고했습니다. Cosium / spring-data-jpa-entity-graph를 바꿀없다는 것을 알았습니다 . JpaRepository엔터티 그래프를 쿼리에 전달할 메서드를 확장 하고 추가합니다. 그런 다음 런타임시 동적 엔터티 그래프를 작성하여 필요한 데이터에 대해서만 왼쪽 조인을 추가 할 수 있습니다.

데이터 흐름은 다음과 같습니다.

  1. GraphQL 요청 수신
  2. GraphQL 요청을 구문 분석하고 쿼리에서 엔티티 그래프 노드 목록으로 변환
  3. 감지 된 노드에서 엔티티 그래프를 작성하고 실행을 위해 저장소로 전달하십시오.

엔티티 그래프에 유효하지 않은 노드를 포함시키지 않는 문제 (예 __typename: graphql) 를 해결하기 위해 엔티티 그래프 생성을 처리하는 유틸리티 클래스를 작성했습니다. 호출 클래스는 그래프를 생성하는 클래스 이름을 전달한 다음 ORM이 유지 관리하는 메타 모델에 대해 그래프의 각 노드를 검증합니다. 노드가 모델에 없으면 그래프 노드 목록에서 제거합니다. (이 점검은 재귀 적이어야하며 각 자녀도 점검해야합니다)

이것을 발견하기 전에 Spring JPA / Hibernate 문서에서 권장되는 프로젝션과 다른 모든 대안을 시도했지만 적어도 추가 코드로 문제를 우아하게 해결하지는 못했습니다.


수퍼 유형에서 알려지지 않은 연관을로드하는 문제를 어떻게 해결합니까? 또한 다른 답변에서 알 수 있듯이 순수한 JPA 솔루션이 있는지 알고 싶지만 lib는 selected모든 하위 유형에 대해 연결을 사용할 수없는 것과 동일한 문제가 있다고 생각합니다 value.
스턱

GraphQL에 관심이 있다면, Blaze-Persistence Entity Views와 graphql-java를 통합했습니다 : persistence.blazebit.com/documentation/1.5/entity-view/manual/…
Christian Beikov

@ChristianBeikov 덕분하지만 우리는 우리의 모델 / 방법에서 프로그래밍 방식으로 우리의 스키마를 생성하는 SQPR를 사용
aarbor

코드 우선 접근 방식을 좋아한다면 GraphQL 통합을 좋아할 것입니다. 실제로 사용되는 열 / 표현식 만 가져 오기 조인 등을 자동으로 가져 오는 것을 처리합니다.
Christian Beikov

0

귀하의 의견에 따라 수정 :

사과드립니다. 첫 번째 라운드에서 문제를 이해하지 못했습니다. 문제는 스프링 데이터를 시작할 때뿐만 아니라 findAll ()을 호출하려고 할 때만 발생합니다.

따라서 이제 github에서 전체 예제를 가져올 수 있습니다. https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/

이 프로젝트 내에서 문제를 쉽게 재현하고 해결할 수 있습니다.

실제로 스프링 데이터와 최대 절전 모드는 기본적으로 "선택된"그래프를 결정할 수 없으므로 선택한 옵션을 수집하는 방법을 지정해야합니다.

먼저 Answer 클래스의 NamedEntityGraphs를 선언해야합니다.

보시다시피, Answer 클래스 의 속성 에 대한 두 개의 NamedEntityGraph 가 있습니다

  • 로드와 특정 관계가없는 모든 Value에 대한 첫 번째

  • 특정 Multichoice 값 의 두 번째입니다 . 이 것을 제거하면 예외가 재현됩니다.

둘째, LAZY 유형의 데이터를 가져 오려면 트랜잭션 컨텍스트 answerRepository.findAll ()에 있어야합니다.

@Entity
@Table(name = "answer")
@NamedEntityGraphs({
    @NamedEntityGraph(
            name = "graph.Answer", 
            attributeNodes = @NamedAttributeNode(value = "value")
    ),
    @NamedEntityGraph(
            name = "graph.AnswerMultichoice",
            attributeNodes = @NamedAttributeNode(value = "value"),
            subgraphs = {
                    @NamedSubgraph(
                            name = "graph.AnswerMultichoice.selected",
                            attributeNodes = {
                                    @NamedAttributeNode("selected")
                            }
                    )
            }
    )
}
)
public class Answer
{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private int id;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "value_id", referencedColumnName = "id")
    private Value value;
// ..
}

문제는 인출되지 않은 value의 -association을 Answer하지만 점점 selected(가) 경우에 협회 value입니다 MCValue. 귀하의 답변에는 이에 관한 정보가 포함되어 있지 않습니다.
스턱

@Stuck 귀하의 답변에 감사드립니다. MCValue 클래스를 나와 공유하십시오. 현지에서 문제를 재현하려고 노력할 것입니다.
bdzzaid

당신은 협회 규정 때문에 예에만 작동 OneToMany등을 FetchType.EAGER하지만 같은 질문에 명시된 모든 단체입니다 LAZY.
스턱

@Stuck 마지막 업데이트 이후 답변을 업데이트했습니다. 답변이 문제를 해결하는 데 도움이되고 선택적 관계를 포함하여 엔터티 그래프를로드하는 방법을 이해하는 데 도움이되기를 바랍니다.
bdzzaid

"솔루션"에는 여전히이 질문의 원래 N + 1 문제가 있습니다. 삽입 및 찾기 메소드를 테스트의 다른 트랜잭션에 배치하면 jpa가 selected모든 응답 에 대해 DB 쿼리를 선행로드하지 않고 발행합니다 .
스턱
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.