Dado o modelo de domínio a seguir, quero carregar todos os Answer
s, incluindo seus Value
s e seus respectivos sub-filhos, e colocá-los em um AnswerDTO
para depois converter para JSON. Eu tenho uma solução funcional, mas ela sofre com o problema N + 1 do qual quero me livrar usando um ad-hoc @EntityGraph
. Todas as associações estão configuradas LAZY
.
@Query("SELECT a FROM Answer a")
@EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();
Usando um ad-hoc @EntityGraph
no Repository
método, posso garantir que os valores sejam pré-buscados para impedir N + 1 na Answer->Value
associação. Enquanto meu resultado é bom, há outro problema N + 1, devido ao carregamento lento da selected
associação dos MCValue
s.
Usando isto
@EntityGraph(attributePaths = {"value.selected"})
falha, porque o selected
campo é obviamente apenas parte de algumas das Value
entidades:
Unable to locate Attribute with the the given name [selected] on this ManagedType [x.model.Value];
Como posso dizer à JPA que apenas tente buscar a selected
associação caso o valor seja a MCValue
? Eu preciso de algo parecido optionalAttributePaths
.
fonte
selected
as respostas que possuem aMCValue
. Não gostava que isso exigisse um loop adicional e precisaria gerenciar o mapeamento entre os conjuntos de dados. Gosto da sua ideia de explorar o cache do Hibernate para isso. Você pode explicar como é seguro (em termos de consistência) confiar no cache para conter os resultados? Isso funciona quando as consultas são feitas em uma transação? Eu tenho medo de erros de inicialização preguiçosos difíceis de detectar e esporádicos.MCValue
entidades. E você não precisa de um loop adicional. Você deve buscar todas asMCValue
entidades com 1 consulta que se associe àAnswer
e use a mesma cláusula WHERE da sua consulta atual. Também falei sobre isso na transmissão ao vivo de hoje: youtu.be/70B9znTmi00?t=238 Começou às 3:58, mas fiz algumas outras perguntas entre ...SINGLE_TABLE_INHERITANCE
.Não sei o que o Spring-Data está fazendo lá, mas para fazer isso, você geralmente precisa usar o
TREAT
operador para poder acessar a sub-associação, mas a implementação para esse Operador é bastante complicada. O Hibernate suporta o acesso implícito à propriedade de subtipos, o que você precisaria aqui, mas aparentemente o Spring-Data não pode lidar com isso corretamente. Eu posso recomendar que você dê uma olhada no Blaze-Persistence Entity-Views , uma biblioteca que funciona sobre a JPA que permite mapear estruturas arbitrárias em relação ao seu modelo de entidade. Você pode mapear seu modelo de DTO de uma maneira segura, além da estrutura de herança. As visualizações de entidade para o seu caso de uso podem ter esta aparênciaCom a integração de dados do Spring fornecida pelo Blaze-Persistence, você pode definir um repositório como este e usar diretamente o resultado
Ele irá gerar uma consulta HQL que seleciona exatamente o que você mapeou no
AnswerDTO
qual é algo como o seguinte.fonte
interface MCValueDTO extends ValueDTO { @Mapping("selected.id") Set<Long> getOption(); }
Meu projeto mais recente usou o GraphQL (o primeiro para mim) e tivemos um grande problema com as consultas N + 1 e tentando otimizar as consultas para ingressar em tabelas somente quando necessárias. Eu encontrei Cosium / spring-data-jpa-entity-graph insubstituível. Ele estende
JpaRepository
e adiciona métodos para passar um gráfico de entidade para a consulta. Em seguida, você pode criar gráficos de entidade dinâmicos em tempo de execução para adicionar uniões esquerdas para apenas os dados necessários.Nosso fluxo de dados é mais ou menos assim:
Para resolver o problema de não incluir nós inválidos no gráfico da entidade (por exemplo,
__typename
do graphql), criei uma classe de utilitário que lida com a geração do gráfico da entidade. A classe de chamada passa no nome da classe para a qual está gerando o gráfico, que valida cada nó no gráfico com relação ao metamodelo mantido pelo ORM. Se o nó não estiver no modelo, ele será removido da lista de nós do gráfico. (Essa verificação precisa ser recursiva e verificar cada criança também)Antes de descobrir isso, tentei projeções e todas as alternativas recomendadas nos documentos do Spring JPA / Hibernate, mas nada parecia resolver o problema com elegância ou pelo menos com uma tonelada de código extra
fonte
selected
associação não está disponível para todos os subtipos devalue
.Editado após o seu comentário:
Peço desculpas, não subestimei seu problema na primeira rodada, seu problema ocorre na inicialização dos dados da primavera, não apenas quando você tenta chamar o findAll ().
Portanto, agora você pode navegar pelo exemplo completo que pode ser obtido no meu github: https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/
Você pode facilmente reproduzir e corrigir seu problema neste projeto.
De fato, os dados do Spring e o hibernate não são capazes de determinar o gráfico "selecionado" por padrão e você precisa especificar a maneira de coletar a opção selecionada.
Então, primeiro, você deve declarar o NamedEntityGraphs da classe Answer
Como você pode ver, há dois NamedEntityGraph para o valor do atributo da classe Answer
O primeiro para todos os Value sem relação específica para carregar
O segundo para o valor específico de escolha múltipla . Se você remover este, você reproduzirá a exceção.
Segundo, você precisa estar em um contexto transacional answerRepository.findAll () se quiser buscar dados no tipo LAZY
fonte
value
de,Answer
mas obter aselected
associação casovalue
seja aMCValue
. Sua resposta não inclui nenhuma informação sobre isso.OneToMany
comoFetchType.EAGER
mas como indicado na pergunta: todas as associações sãoLAZY
.selected
para todas as respostas, em vez de carregá-las antecipadamente.