Alguém usa algum padrão diferente de Open Session In View ao trabalhar com JPA?

Alguém usa algum padrão diferente de Open Session In View ao trabalhar com JPA?

:smiley:

No lugar de usar OpenSessionInView, que é na minha opinião um dos antipatten mais perigosos com JPA + JSF (digo isso, porque já conheci muita gente reclamando da performance do JPA com JSF por não conhecerem as conseqüências desse pattern), você pode usar OpenTransactionInApplicationPhaseListener.

Diferente do OpenSessionInView, o OpenTransactionInApplicationPhaseListener é um “pouquinho” mais difícil de implementar e demanda do desenvolvedor um entendimento CLEAR de cada consulta ao banco de dados. Com certeza você terá mais trabalho e gastará mais tempo para desenvolver utilizando este filtro, mas sua aplicação terá um bom desempenho, pode ter certeza.

O problema do OpenSessionInView é que ele facilita a aparição do famoso “N + 1”. Por exemplo, se você acessa algum dado lazy em uma datatable JSF com OpenSessionInView, quando o JSF for renderizar o datatable, ele fará uma consulta a mais ao banco de dados para cada registro renderizado no datatable!!! Com o OpenTransactionInApplicationPhaseListener, você receberá um lazyInitializationException, o que não é ruim, pois sinaliza para você que sua query precisa ser PLANEJADA. Nada impede que o programador PLANEJE suas consultas utilizando OpenSessionInView, mas como outros programadores menos conscienciosos podem estar envolvidos no projeto, abrir essa brecha pode ser “perigoso”.

Agora, é tudo questão de bom senso. Se a sua aplicação é pequena e você precisa desenvolver rapidamente, o OpenSessionInView é uma alternativa.
O que é preciso entender é que o OpenSessionInView não é para evitar lazyInitializationException, isso é consequencia de outro problema que ele tenta solucionar: a abertura e fechamento de transações.

NÃO VÁ NO MEU BICO, tenho 13 anos de experiência na área, mas isso não quer dizer que o que digo é verdade absoluta. Já fui esclarecido várias vezes por programadores juniors neste forum (váaarias vezes). Tentei ser objetivo, mas não posso negar que as emoções afloram quando falo sobre esse assunto, uma vez que já tive que resolver vários problemas em decorrência dele devido ao trabalho de terceiros.

Como eu gosto de matar a cobra e mostrar o pau, se você se sentir inseguro com o OpenTransactionInApplicationPhaseListener, você pode dar uma olhada no livro do MyFaces, no qual o autor explica com exemplos as conseqüências desse pattern. Eu senti um alívio tão grande quando vi alguém de renome falar sobre esse assunto, pois já sinaliza esses efeitos colaterais do OpenSessionInView faz tempo.

OBS: estou desenvolvendo uma estratégia de abertura e fechamento de transação usando CDI sem a necessidade de outros plugins. Se eu tiver frutos, eu posto no GUJ. É uma solução hibrida.
OBS: como você usa JPA, talvez você tenha interesse em experimentar o conversor de Entidades genérico que desenvolvi e que o povo aqui do GUJ ajudou a melhorar: http://www.guj.com.br/java/220692-para-voce-entityconverter-para-qualquer-entidade-e-tipo-de-id

Abraço

Interessante…

Ola Flavio

Sua preocupacao com o n+1 é mais que valida. Mas isso não é relacionado ao open session in view diretamente. Mesmo sem mante-la aberta, se voce puxar uma entidade e chamar seus getters lazy, vai continuar tendo n+1 queries. Isso é: o responsavel pelo n+1 queries certamente é o lazy, e nao o OpenSessionInView.

Sem contar que, com JSF, o problema piora muito: algumas implementações disparam invocações aos seus getters 3, 4 vezes em um único request, ativando todas as suas proxies lazy, gerando muitas outras consultas. Esse é um problema de implementações do JSF, e não do Hibernate/OpenSessionInView.

Gostei da sua palavra: tem de ser planejado. Nao usar o lazy apenas pela magica. Alias, essa é a abordagem do pessoal do Objectify em relação ao BigTable do google: não há nada lazy, nada é feito sem você explicitamente pedir para carregar, dessa forma fica claro a quantidade de queries que você está disparando.

abracos

Então para se trabalhar com Hibernate/Jpa + JSF, a melhor solução seria utilizar OpenTransactionInApplicationPhaseListener ?!
Pois a utilização de EAGER para todos os lados mata a aplicação, então qual seria a melhor maneira de evitar os EAGER utilizando JPA + JSF ?!

Att,
Renan Montenegro

Pois é Renan: colcoar tudo eager é impossível. Colocar tudo lazy e depois ficar fazendo fetch join para todas as queries seria também muito, muito trabalhoso.

Mas, mesmo com OpenSessionInView, se voce conhecer bem do hibernate, e fizer os ajustes necessarios no fetch-size/batch-size, além de configurar o second level cache para as coleções e queries, não apenas as entidades, pode diminuir o problema do n+1 para zero sem ter de tomar uma atitude drástica como fechar a session rapidamente.

Essa discussão se aplica tb a implementação de API’s web (tipo Twitter) que manipulam modelo persistente. Uma vez que a entidade quente chega ao marshaller (seja ele para XML ou JSon), pra montar o resultado todos os get’s são invocados e a chance de problemas na inicialização de atributos lazy é mto grande.

No meu caso, a solução (pouco elegante) que adotamos foi manter, paralelo ao modelo persistente, um conjunto de VO’s. Isso nos deu mais controle sobre a profundidade da informação no retorno e ajuda a contornar os LazyInitializationException. O contraponto é a chateação de manter dois modelos paralelos e a necessidade de criação de adaptadores entity/VO.

Apenas um detalhe: o problema do open session in view tambem ocorre com frameworks action-like. Nao eh especifico do faces somente.

Alem do que, se nao estou enganado, esse pattern abre uma session do Hibernate, nao necessariamente uma transaçao com o banco a cada request.

E lembrando que sempre devemos fazer da maneira mais esperta possivel: podemos fazer open session in view sem abrir uma session/transaction logo de cara, deixamos isso tambem lazy, e so abrimos se alguem vai utilizar, evitando ate mesmo a necessidade de demarcacao de que metodos vao querer session

Pois é, esse problema de granularidade ocorre em uma gama de outros lugares, sempre quando temos dois tiers diferentes envolvidos. Devemos minimizar a quantidade de informacoes, mas mais importante: o numero de roundtrips entre um tier e outro, evitando, em especial, n+1 roundtrips, com N variavel dependendo de dados. Isso pode facilmente derrubar servidores, mesmo com pouca carga no tier do cliente.

Eu evito ao maximo criar uma hierarquia paralela do model, pela dificuldade de manutencao. Como o Rafael Ponte disse no twitter, mesmo quando os dtos sao necessarios, voce nao precisa fazer um mapeamento de DTOS 1 para 1 em relacao a cada entidade que voce tem. Se justamente o problema é granularidade do servico, voce vai criar alguns poucos DTOs que carreguem bastante informacao (nem mais, nem menos do que o outro tier precisa), numa tacada so.

Numa aplicação que já se encontra cheio de EAGER, qual seria a melhor solução, realmente aplicar o OpenSessionInView ?! Lembrando que a aplicação é em JSF.

Att,
Renan Teixeira Lima Verde Montenegro

Oi Renan

Se voce percebeu que o monte de eager ja esta carregando muito mais dados do que o necessario (como ver montanhas de sqls gigantes sendo disparadas quando o show_sql esta true, mesmo para um simples load de uma entidade), realmente usar o opensessioninview + colocar algumas relacoes como lazy podem melhorar muito o numero de joins e queries. Mas as vezes alguns poucos @Cache em entidades e relacionamentos podem ajudar bastante. É preciso balancear.

Paulo, talvez eu não tenha entendido corretamente a sua idéia, mas quando você diz “sem mante-la aberta”, você está se referindo a Sesison/EntityManager? Se for, se ela não estiver aberta, acorrerá o Lazy Initialization Exception, ou seja, nem consulta N+1 será executada.

Rafael, realmente o problema do N+1 não é exclusivo do JSF, tampouco de qualquer outro framework, uma vez que você consegue gerar consultas N+1 em uma classe java standalone. Como trabalho com JSF, a minha idéia era evidenciar o problema que ocorre muitas vezes quando uma coleção Lazzy é renderizada em um Datatable ou qualquer componente durantente a fase de renderização da view (exemplo: apontar a coleção de uma entidade diretamente na datatable). Além disso, para os mais puristas (não sou purista, sou um cara prático :), OpenSessionInView não se coaduna com a arquitetura em camadas, uma vez que você pode acessar seu banco de dados na camada de visão (RENDER VIEW). Esse pattern, em sua versão original http://community.jboss.org/wiki/OpenSessioninView faz o controle de transação.

Outro problema que não citei (baseado no pattern original), mas basta perguntar para qualquer um que já o utilizou é o que fazer quando há um erro durante a transação? O rollback fica no filtro, então, fica mais difícil dar uma mensagem específica ou controlar o fluxo da aplicação. OpenTransactionInApplicationPhaseListener sofre deste mesmo problema.

Um primeiro passo para que se possa abandonar este filtro ou minimizar seus efeitos é verificar cada consulta minuciosamente, PLANEJANDO-A sem pudor. Existe outra coisa, muito polêmica, mas que procurarei primeiro onde está a referência (não lembro) deste comentário para colar aqui. Eu sempre gosto de matar a cobra e mostrar o pau.

Apesar de não ter “liberdade” de abandonar JPA, darei uma olhada “com carinho” no Objectify, pois compartilho da mesma visão. Valeu pela dica!

Renan, não é 8 nem 80, é planejamento. É por isso que o OpenSessionInView é simples, se você não planejar nada, ele funcionará, mas poderá acontecer os problemas que listei neste tópico.

Acho muito válido a participação de todos, porque esse é um assunto que precisava ser revisado.

Paulo, talvez eu não tenha entendido corretamente a sua idéia, mas quando você diz “sem mante-la aberta”, você está se referindo a Sesison/EntityManager? Se for, se ela não estiver aberta, acorrerá o Lazy Initialization Exception, ou seja, nem consulta N+1 será executada.

[/quote]

Nao expressei bem mesmo. quis dizer que, mesmo nao mantendno aberta a session na view, porem chamando seus getters lazy antes de fecha-la (isso é, antes da view, atraves de Hibernate.initialize, ou invocando os getters lazy diretamente) voce vai passar pelos problemas do n+1. Por isso disse que nao é o open session in view o unico que pode gerar n+1. Mesmo sem open session in view isso pode acontecer.

Excelente discussao mesmo. precisa planejar

Entendi Paulo. Realmente, N+1 continua acontecendo. É como coloquei respondi acima: é possível gerar N+1 em uma classe java standalone, independente da combinação com outros frameworks.

Vai aqui um apelo para o pessoal que está começando a usar JPA/Hibernate: planejem suas consultas primeiro, para depois pensarem no uso de cache de segundo nível.
Mais uma vez, apareceram problemas em decorrência deste pattern para eu resolver.

[quote=Flavio Almeida]Vai aqui um apelo para o pessoal que está começando a usar JPA/Hibernate: planejem suas consultas primeiro, para depois pensarem no uso de cache de segundo nível.
Mais uma vez, apareceram problemas em decorrência deste pattern para eu resolver.
[/quote]

Oi Flávio, bom dia. Poderia explanar a situação que está enfrentando, citando código e a idéia de como você faria… ???

Abraço,

[quote=andredecotia]Alguém usa algum padrão diferente de Open Session In View ao trabalhar com JPA?
:smiley: [/quote]
Se vc continuar usando recursos LAZY não, caso contrario sim.

Posso claro, mas devo fazer isso durante a semana, pois estou refatorando um sistema, motivo pelo qual “desabafei” aqui.
Assim que entregar o prometido, tentarei, em poucas palavras, dizer o que eu enfrentei e como resolvi a situação.

andredecotia, não entrarei nos pormenores como eu resolvo os problemas que apontei, por uma questão de tempo, mas em linhas gerais, eu faço o seguinte:

  • uso CDI para controlar o ciclo de vida do EntityManager ou Session do Hibernate. Aqui eu ataco o problema do gerenciamento do EntityManager, que passa a ficar sob responsabilidade do contêiner;

  • uso um CDI custom escope que é menor que o de request, ou seja, ele não deixa o EntityManager ou a Session disponível durante a fase de renderização, porque eu não quero acessar a camada de infraestrutura na view. Caso você não tenha experiência em criar um escopo costumizado em CDI, você pode usar o @RequestScoped, mas isso ainda permite que o desenvolvedor acesse objetos com inicialização lazy durante a fase de renderização, mas de maneira NÃO transacional (isso porque não queremos abrir e fechar a transação por escopo, certo?), uma outra má prática;

  • uso um CDI Interceptor simples, para abrir e fechar a transação atomicamente. Como OpenSessionInView, a transação, na maioria das vezes, é feita no próprio filter, tornando-se um problema quando você precisa tratar alguma exceção disparada durante o commit;

  • Planejo minhas consultas, geralmente, voltadas para determinado caso de uso, desta forma, eu não deixo chegar na view objetos com inicialização tipo lazy, além de otimizar a consulta, uma vez que retornar uma lista da entidade X nem sempre é a melhor opção.

O que é importante aqui é deixar uma infraestrutura que “force” o desenvolvedor a planejar suas consultas, desta forma, evitar OpenSessionInView é conseguir tal infraestrutura. Você pode continuar a usar OpenSessionInView (como os problemas que relatei) e planejar as suas consultas, caso não queria ou não possa interferir na forma com que o sistema foi organizado, mas isso permitirá que outros desenvolvedores, menos conscienciosos, realizam barbaridades.

Se você puder utilizar CDI, uma especificação Java, sem hiato, escolha a implementação da Apache, porque, na minha prática, ela é superior a implementação da JBoss (Weld).

andredecotia, só te peço uma coisa, de coração, não vá no meu bico, tenha a sua própria experiência. Você pode sentir na prática que tudo isso que eu falei não vale o esforço e por mais que eu defenda que tudo isso vale a pena, jamais poderei ir contra a sua experiência. As pessoas esquecem que a personalidade do programador tem relação com o código que ele escreve e como ele percebe o resultado qualitativamente. Além de desenvolvedor (formado, pós-graduado, blá, blá, blá) tenho formação em psicologia e estou escrevendo uma artigo sobre como usar ACP no gerenciamento de projetos Ágeis, algo inédito até então. Aproveitei para fazer uma propaganda, desculpe :slight_smile:

Alguns dias atrás me deparei com a questão de como fazer o controle de transações em uma aplicação web utilizando Hibernate. Resolvi ao invés de utilizar o Open Session In View, inserir um pouco de Spring na aplicação e deixar com ele o controle de transações. Até o momento sem maiores problemas com esta combinação.