Por que o Hibernate Open Session no View é considerado uma prática ruim?


108

E que tipo de estratégia alternativa você usa para evitar LazyLoadExceptions?

Eu entendo que a sessão aberta em vista tem problemas com:

  • Aplicativos em camadas em execução em diferentes jvm's
  • As transações são confirmadas apenas no final e provavelmente você gostaria dos resultados antes.

Mas, se você sabe que seu aplicativo está sendo executado em uma única VM, por que não aliviar sua dor usando uma sessão aberta na estratégia de visualização?


12
OSIV é considerado uma má prática? Por quem?
Johannes Brodwall

4
E - quais são as boas alternativas?
David Rabinowitz

7
Esta parte do texto é de desenvolvedores de costura: Existem vários problemas com esta implementação, o mais sério é que nunca podemos ter certeza de que uma transação será bem-sucedida até que a confirmemos, mas quando a transação "abrir sessão em exibição" for confirmada, a visualização está totalmente renderizada e a resposta renderizada pode já ter sido enviada para o cliente. Como podemos notificar o usuário de que sua transação não foi bem-sucedida?
darpet


2
Veja esta postagem do blog para prós e contras e minha própria experiência sobre isso - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Respostas:


46

Porque o envio de proxies possivelmente não inicializados, especialmente coleções, na camada de visualização e o acionamento do carregamento de hibernação a partir daí pode ser problemático tanto do ponto de vista de desempenho quanto de compreensão.

Entendendo :

O uso do OSIV 'polui' a camada de visualização com questões relacionadas à camada de acesso a dados.

A camada de visualização não está preparada para lidar com o HibernateExceptionque pode acontecer durante o carregamento lento, mas presumivelmente a camada de acesso a dados está.

Desempenho :

OSIV tende a puxar o carregamento de entidade adequado para baixo do tapete - você tende a não notar que suas coleções ou entidades são inicializadas lentamente (talvez N + 1). Mais conveniência, menos controle.


Atualização: veja o antipadrão OpenSessionInView para uma discussão mais ampla sobre este assunto. O autor lista três pontos importantes:

  1. cada inicialização lenta obterá uma consulta, o que significa que cada entidade precisará de N + 1 consultas, onde N é o número de associações lazy. Se sua tela apresenta dados tabulares, ler o registro do Hibernate é uma grande dica de que você não deve fazer o que deveria
  2. isso derrota completamente a arquitetura em camadas, já que você suja suas unhas com DB na camada de apresentação. Este é um golpe conceitual, então eu poderia viver com ele, mas há um corolário
  3. por último, mas não menos importante, se ocorrer uma exceção durante a busca da sessão, ela ocorrerá durante a gravação da página: você não pode apresentar uma página de erro limpa ao usuário e a única coisa que você pode fazer é escrever uma mensagem de erro no corpo

13
Ok, isso 'polui' a camada de visualização com exceção de hibernação. Mas, em relação ao desempenho, acho que o problema é bem parecido do que acessar uma camada de serviço que vai retornar seu dto. Se você enfrentar um problema de desempenho, deve otimizar esse problema específico com uma consulta mais inteligente ou um dto mais leve. Se você tiver que desenvolver muitos métodos de serviço para lidar com as possibilidades que você pode precisar na visualização, você também estará 'poluindo' a camada de serviço. não?
HeDinges

1
Uma diferença é que atrasa o fechamento da sessão do Hibernate. Você vai esperar que o JSP seja renderizado / escrito / etc, e isso mantém os objetos na memória por mais tempo. Isso pode ser um problema, especialmente se você precisar gravar dados no commit da sessão.
Robert Munteanu

8
Não faz sentido dizer que o OSIV prejudica o desempenho. Que alternativas existem, exceto para usar DTOs? Nesse caso, você sempre terá um desempenho inferior porque os dados usados ​​por qualquer visualização terão que ser carregados mesmo para visualizações que não precisam deles.
Johannes Brodwall

11
Acho que a poluição funciona ao contrário. Se eu precisar carregar os dados com antecedência, a camada lógica (ou pior, a camada de acesso a dados) precisa saber de que maneira um objeto será exibido. Mude a visualização e você acaba carregando coisas que não precisa ou faltando objetos de que precisa. Uma exceção do Hibernate é um bug e tão prejudicial quanto qualquer outra exceção inesperada. Mas o desempenho é um problema. Problemas de desempenho e escalabilidade forçarão você a pensar e trabalhar mais em sua camada de acesso a dados e, possivelmente, forçar o encerramento da sessão mais cedo
Jens Schauder

1
@JensSchauder "Mude a visão e você acaba carregando coisas que não precisa ou faltando objetos de que precisa". É exatamente isso. Se você mudar a visão, é muito melhor carregar coisas que você não precisa (já que é mais provável que você esteja ansioso para buscá-los) ou descobrir os objetos ausentes, pois obteria a exceção de carregamento lento, do que deixar a visão carregar preguiçosamente, pois isso resultará no problema N + 1, e você nem saberá que está acontecendo. Então, IMO, é melhor que a camada de serviço (e você) saiba o que é enviado do que a exibição carregando preguiçosamente e você não sabe nada sobre isso.
Jeshurun,

40

Para obter uma descrição mais detalhada, você pode ler meu artigo Abrir sessão no antipadrão de visualização . Caso contrário, aqui está um resumo de porque você não deve usar Open Session In View.

Open Session In View tem uma abordagem inadequada para buscar dados. Em vez de permitir que a camada de negócios decida como é melhor buscar todas as associações que são necessárias para a camada de Visualização, ela força o Contexto de Persistência a permanecer aberto para que a camada de Visualização possa acionar a inicialização do Proxy.

insira a descrição da imagem aqui

  • O OpenSessionInViewFilterchama o openSessionmétodo do subjacente SessionFactorye obtém um novo Session.
  • O Sessionestá vinculado ao TransactionSynchronizationManager.
  • O OpenSessionInViewFilterchama doFilterda javax.servlet.FilterChainreferência do objeto e a solicitação é processada posteriormente
  • O DispatcherServleté chamado e encaminha a solicitação HTTP para o subjacente PostController.
  • O PostControllerchama PostServicepara obter uma lista de Postentidades.
  • O PostServiceabre uma nova transação e HibernateTransactionManagerreutiliza a mesma Sessionque foi aberta pelo OpenSessionInViewFilter.
  • O PostDAObusca a lista de Postentidades sem inicializar nenhuma associação preguiçosa.
  • O PostServiceconfirma a transação subjacente, mas Sessionnão é fechado porque foi aberto externamente.
  • O DispatcherServletcomeça a renderizar a IU, que, por sua vez, navega pelas associações lazy e dispara sua inicialização.
  • O OpenSessionInViewFilterpode fechar o Sessione a conexão de banco de dados subjacente também é liberada.

À primeira vista, pode não parecer uma coisa terrível de se fazer, mas, uma vez que você visualiza da perspectiva do banco de dados, uma série de falhas começa a se tornar mais óbvia.

A camada de serviço abre e fecha uma transação de banco de dados, mas depois disso, não há nenhuma transação explícita acontecendo. Por esse motivo, cada instrução adicional emitida na fase de renderização da IU é executada no modo de confirmação automática. A confirmação automática pressiona o servidor de banco de dados porque cada instrução deve liberar o log de transações para o disco, causando, portanto, muito tráfego de E / S no lado do banco de dados. Uma otimização seria marcar o Connectioncomo somente leitura, o que permitiria ao servidor de banco de dados evitar a gravação no log de transações.

Não há mais separação de interesses porque as instruções são geradas tanto pela camada de serviço quanto pelo processo de renderização da IU. Escrever testes de integração que afirmam o número de instruções que estão sendo geradas requer passar por todas as camadas (web, serviço, DAO), enquanto o aplicativo é implantado em um contêiner da web. Mesmo ao usar um banco de dados na memória (por exemplo, HSQLDB) e um servidor web leve (por exemplo, Jetty), esses testes de integração serão mais lentos para executar do que se as camadas fossem separadas e os testes de integração de back-end usassem o banco de dados, enquanto o Os testes de integração front-end estavam simulando a camada de serviço como um todo.

A camada de IU é limitada a associações de navegação que podem, por sua vez, acionar N + 1 problemas de consulta. Embora o Hibernate ofereça @BatchSizeassociações de busca em lotes e FetchMode.SUBSELECTpara lidar com este cenário, as anotações estão afetando o plano de busca padrão, portanto, são aplicadas a todos os casos de uso de negócios. Por esse motivo, uma consulta da camada de acesso a dados é muito mais adequada porque pode ser adaptada aos requisitos atuais de busca de dados do caso de uso.

Por último, mas não menos importante, a conexão com o banco de dados pode ser mantida durante toda a fase de renderização da IU (dependendo do modo de liberação da conexão), o que aumenta o tempo de concessão da conexão e limita o rendimento geral da transação devido ao congestionamento no pool de conexão do banco de dados. Quanto mais a conexão for mantida, mais outras solicitações simultâneas irão esperar para obter uma conexão do pool.

Portanto, ou você obtém a conexão mantida por muito tempo, ou adquire / libera várias conexões para uma única solicitação HTTP, colocando pressão no pool de conexão subjacente e limitando a escalabilidade.

Spring Boot

Infelizmente, Open Session in View é habilitado por padrão no Spring Boot .

Portanto, certifique-se de que, no application.propertiesarquivo de configuração, você tenha a seguinte entrada:

spring.jpa.open-in-view=false

Isso desabilitará o OSIV, para que você possa lidar da LazyInitializationExceptionmaneira certa .


3
Usar Open Session in View com auto-commit é possível, mas não da maneira que foi planejado pelos desenvolvedores do Hibernate. Portanto, embora Open Session in View tenha suas desvantagens, o auto-commit não é um, porque você pode simplesmente desligá-lo e ainda usá-lo.
stefan.m

Você está falando sobre o que acontece dentro de uma transação, e isso é verdade. Mas a fase de renderização da Camada da Web acontece fora do Hibernate, portanto, você obtém o modo autocommit. Faz sentido?
Vlad Mihalcea

Eu acho que é uma variante que não é a ideal para Open Session in View. A sessão e a transação devem permanecer abertas até que a visualização seja renderizada, então não há necessidade de modo de confirmação automática.
stefan.m

2
A sessão permanece aberta. Mas a transação não. Estender a transação ao longo de todo o processo também não é o ideal, pois aumenta seu comprimento e os bloqueios são mantidos por mais tempo do que o necessário. Imagine o que aconteceria se a visualização gerasse uma RuntimeException. A transação será revertida porque a renderização da IU falhou?
Vlad Mihalcea

Muito obrigado pela resposta muito detalhada! Eu só mudaria o guia no final, já que os usuários do Spring Boot provavelmente não usarão o jpa dessa forma.
Skeeve de

24
  • as transações podem ser confirmadas na camada de serviço - as transações não estão relacionadas ao OSIV. É o Sessionque permanece aberto, não uma transação - em execução.

  • se as camadas do seu aplicativo estão espalhadas por várias máquinas, você praticamente não pode usar o OSIV - você deve inicializar tudo o que precisa antes de enviar o objeto pela rede.

  • OSIV é uma maneira agradável e transparente (ou seja - nenhum dos seus códigos sabe que isso acontece) de usar os benefícios de desempenho do carregamento lento


2
Com relação ao primeiro ponto, pelo menos não é verdade para o OSIV original do wiki JBoss, ele também lida com a demarcação da transação em torno da solicitação.
Pascal Thivent

@PascalThivent Qual parte o fez pensar assim?
Sanghyun Lee

13

Eu não diria que Open Session In View é considerado uma prática ruim; o que te dá essa impressão?

Open-Session-In-View é uma abordagem simples para lidar com sessões com o Hibernate. Porque é simples, às vezes é simplista. Se você precisa de um controle refinado sobre suas transações, como ter várias transações em uma solicitação, Open-Session-In-View nem sempre é uma boa abordagem.

Como outros apontaram, existem algumas desvantagens para OSIV - você está muito mais sujeito ao problema N + 1 porque é menos provável que perceba quais transações está iniciando. Ao mesmo tempo, significa que você não precisa alterar sua camada de serviço para se adaptar a pequenas mudanças em sua visualização.


5

Se estiver usando um contêiner de Inversão de Controle (IoC), como o Spring, você pode querer ler sobre o escopo do bean . Essencialmente, estou dizendo ao Spring para me dar um Sessionobjeto Hibernate cujo ciclo de vida abrange toda a solicitação (ou seja, ele é criado e destruído no início e no final da solicitação HTTP). Não preciso me preocupar em LazyLoadExceptionfechar a sessão, pois o contêiner IoC gerencia isso para mim.

Conforme mencionado, você terá que pensar sobre os problemas de desempenho do N + 1 SELECT. Você sempre pode configurar sua entidade Hibernate posteriormente para fazer o carregamento de junção antecipada em locais onde o desempenho é um problema.

A solução de escopo do bean não é específica do Spring. Eu sei que o PicoContainer oferece a mesma capacidade e tenho certeza que outros containers IoC maduros oferecem algo semelhante.


1
Você tem um ponteiro para uma implementação real das sessões do Hibernate disponibilizadas na visualização por meio de beans com escopo de solicitação?
Marvo

4

Em minha própria experiência, OSIV não é tão ruim. O único arranjo que fiz foi usar duas transações diferentes: - a primeira, aberta na "camada de serviço", onde tenho a "lógica de negócios" - a segunda aberta imediatamente antes da exibição da exibição


3

Acabei de postar algumas orientações sobre quando usar a sessão aberta em vista no meu blog. Verifique se estiver interessado.

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/


1
Como regra geral do SO, se você estiver fornecendo uma resposta, é melhor fazer mais do que apenas criar um link para outro lugar. Talvez forneça uma ou duas frases ou itens listados dando a essência. Não há problema em vincular, mas você deseja fornecer um valor extra. Caso contrário, você pode querer apenas comentar e colocar o link lá.
DWright

vale a pena ler o link nesta resposta, ele fornece uma boa orientação sobre quando usar OSIV e não
ams

1

Estou muito enferrujado no Hibernate .. mas acho que é possível ter várias transações em uma sessão do Hibernate. Portanto, os limites da sua transação não precisam ser os mesmos dos eventos de início / parada da sessão.

OSIV, imo, é principalmente útil porque podemos evitar escrever código para iniciar um 'contexto de persistência' (também conhecido como sessão) toda vez que a solicitação precisa fazer um acesso ao banco de dados.

Em sua camada de serviço, você provavelmente precisará fazer chamadas para métodos que têm necessidades de transação diferentes, como 'Requerido, Novo requerido, etc.' A única coisa que esses métodos precisam é que alguém (ou seja, o filtro OSIV) tenha iniciado o contexto de persistência, de modo que a única coisa com que eles precisam se preocupar é - "ei, dê-me a sessão de hibernação para este tópico. Eu preciso fazer alguns Coisas DB ".


1

Isso não vai ajudar muito, mas você pode verificar meu tópico aqui: * Hibernate Cache1 OutOfMemory com OpenSessionInView

Eu tenho alguns problemas de OutOfMemory por causa do OpenSessionInView e muitas entidades carregadas, porque eles permanecem no cache Hibernate level1 e não são coletados como lixo (carrego muitas entidades com 500 itens por página, mas todas as entidades permanecem no cache)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.