Ser Lazy (preguiçoso) ou não ser?

A discussão aqui é se vale a pena usar Lazy Loading automático ou se é melhor fazer o lazy loading na mão mesmo.

Imagine um Carro que contém um object Motor dentro dele.


public class Carro {

    private int id;

    private String name;

    private String chassi;

    private int motor_id;

    private Motor motor;

    // getters and setters

}

public class Motor {

     private int id;

     private String tipo;

     private String potencia;

     // getters and setters
}

O conceito de Lazy Loading, dita que quando vc carregar o Carro do banco-de-dados, o Motor não será carregado imediatamente, mas apenas após o método getMotor() ser chamado por alguém.

Ou seja, se ninguém precisar do motor, o motor nunca será carregado do banco de dados.

A primeira vista isso parece ser uma coisa maravilhosa. Vc para e pensa: “Estou economizando acesso ao banco-de-dados!” Se meu carro tivesse outros 10 objetos dentro dele (lista de peças, rodas, bancos, etc), nenhum desses objetos seria carregado de primeira, ou seja, eu economizaria tempo, banco-de-dados, e a coisa seria feita sob demanda, a medida que alguém precisasse desses objetos e chamasse seu método get.

Agora vamos pensar por uma outra ótica:

  1. Para a solução de ORM (Hibernate, iBatis, etc) implementar isso é necessário o uso de PROXIES. Isso porque quando alguém chamar o getMotor(), precisamos interceptar isso, fazer o load do motor, colocar o motor no Carro e retornar a execução para o método normal getMotor(). Para implementar proxies em Java vc precisa da implementação do JDK ou de CGLIB. Se for usar a implementação do JDK, o seu objeto (bean) precisa ser uma interface, ou seja, na minha opinião um preço muito grande a se pagar! Se for usar CGLIB, vc (e a jvm que for executar isso) precisa se sentir confortável com alteração de bytecode, para executar esse proxy “por trás dos panos”. Lembre-se que tudo isso tem um custo de performance, que concordo “muitas vezes” pode ser ignorado.

  2. LazyLoading acaba levando a uma prática não muito boa, que é fazer acessos ao banco-de-dados enquando uma página JSP está sendo executada. O que acontecerá se uma exception for lançada ali devido a algum erro de acesso ao banco de dados. O seu framework e a sua aplicação web estão prontos para tratar isso de forma correta? Não seria melhor que todo esse trabalho sujo fosse feito na action e quando chegássemos no JSP tudo já estivesse prontinho, ou seja, o JSP é apenas uma coisa burra que pega os resultados e exibe?

  3. LazyLoading exige que vc (ou o seu framework) não feche a sessão/connection até que o JSP seja completamente executado, caso contrário vc vai ganhar um LazyInstantiationException. Ou o seu framework faz isso pra vc através de um filtro, ou vc vai ter que implementar isso não mão, provavelmente através de um ServletFilter.

Diante dos pontos acima, fica a pergunta. Será LazyLoading automático realmente uma coisa necessária e maravilhosa? Não seria melhor deixamor nós de sermos Lazy (preguiçosos) e controlarmos nós mesmos quem deve ser carregado e quando?

Voltemos ao exemplo do Carro com Motor.

Se eu sei que vou fazer um forward para uma página JSP que não vai precisar do motor do carro, ou seja, vai usar apenas o carro e não as informações do motor, eu não preciso me preocupar em carregar o motor.


public String execute() throws Exception {

      Carro carro = new Carro(23);

      session.load(carro); // objeto motor não vai ser carregado...

      output.setValue("carro", carro);

     return SUCCESS;
}

E se eu sei que a minha página JSP vai precisar das informações do motor eu simplesmente carrego o motor eu mesmo: (ou quem sabe pego de um cache???)


public String execute() throws Exception {

      Carro carro = new Carro(23);

      session.load(carro);

      Motor motor = new Motor(carro.getMotorId());

      session.load(motor);

      carro.setMotor(motor);

      output.setValue("carro", carro);

      return SUCCESS;
}

A questão para debate é:

Precisamos realmente de LazyLoading automático ou devemos deixar de ser lazy (preguiçosos) e fazer nós mesmos esse controle de “quem é carregado, quando e onde”? Vc já teve problema com LazyInstantiationException?

Já tive sim saoj, muitos de meus beans são “entrelaçados”, qdo chamo um, posso ou não chamar os “filhos”… já levei uma cacetada de lazyInstantionException…
Solução?
Todas os meus beans que tem alguma coleção eu sempre deixo lazy false.
Resolveu meu problema de vez…
quanto a fazer direto na mão como vc fez, ainda não tinha pensando nisto, somente testando performance é que vou verificar se vale a pena.

Amigo na empresa que trabalho e outra que trabalhei sempre usamos lazy quando temos set de objetos “Coleções”, isso realmente para economizar em memoria, criamos metodos para fazer laze do que precisamos e quando precisarmos, tambem usamos o cache do hibernate ajuda muito, dificilmente usamos proxy pois a necessiade de usar proxy é so quando querermos o id dos pk certo…

meu ponto de vista, usar lazy sim acho uma boa pratica porem cuidados devemos tomar sempre para nao precisar ficar vendo msg de exeção desagradavel na telas. e esse cuidados deve ser tomado assim que vc conhece o negocio digo como funciona as telas se realmente vc vai precisar dos filhoes e assim por diante !

abraços

Custo de perfomance é constante, tendendo a zero quando voce medir o custo da execucao do SQL. Quem tem de se sentir confortavel com a proxy é o hibernate, e nao eu… ainda bem que nem preciso ver isso na minha frente quando uso hibernate.

No hibernate, voce pode, programaticamente, modificar se quer pegar algo que era lazy como eager.

Nao exige. A conexao podera ser fechada, e a session abre outra na proxima transacao/query. Na verdade, o legal eh que o hibernate abstrai isso tudo pra mim. Pouco me importa o pooling e managing de connection dele, eqto estou programando. O entity manager tambem pode ser fechado, e alguns vendors, como o toplink, reabrem se necessario. Pessoalmente nao gosto.

Se nada for lazy, todo e qualquer select vai levantar metade do banco de dados em memoria. Eu prefiro colocar o que der eager, desde que nao seja colecao ou que o caminho no grafo fique longo, e evitar ao maximo as colecoes no outro lado da ponta.

Tipica decisao pra ser deixada pro framework, nao pra mim. A ideia de um framework é ele trabalhar por mim, e nao o contrario. Ficar fazendo if para verificar se tem o objeto no cache é mortal, vai dar spagueti.

A discussão não é se lazy loading é bom ou ruim. Lazy loading é fundamental / essencial.

A questão é se o cara deve deixar o framework fazer isso de forma automática quando alguém chamar getMotor() ou se é melhor ele mesmo controlar isso, ou seja, ele mesmo carrega as coisas conforme elas forem necessárias.

Como boa prática, acho que os próprios autores do hibernate recomendam que vc force o lazy loading automático antes de dar um forward, ou seja, antes de dar o forward vc faz uma chamada a getMotor().

Se a session do hibernate for fechada antes de acontecer o lazy loading automatíca, ou seja, antes de chamarem getMotor(), vc ganha uma LazyInstantiationException. E o problema é que muitas vezes getMotor() vai ser chamado na view, logo vc tem que ter certeza (ou o seu framework) de que a session só vai ser fechada APÓS a view ser executada.

Documentação do Hibernate:

Usar lazy ou não é uma questão que deve ser analisada pontualmente, dependendo exclusivamente da necessidade naquele determinado momento. Não existe uma regra única, existe a solução mais adequada para cada situação.

A grande vantagem é que o hibernate te permite configurar o padrão e mudar esse padrão em tempo de execução de acordo com a sua necessidade.

O que pode ser mais flexível que isso?

Entendo sua duvida. O que der para deixar o framework fazer, é sempre melhor. Se precisar mudar, deixar opcional. É como eu disse que o hibernate faz: poe LAZY, se quiser, pela query muda pra EAGER.

Acho que não. E deixa o codigo feio demais essas chamadas getters que aprecem nonsense.

Como eu disse, NO ORACLE TOPLINK, ele reabre o entity manager se voce precisar. No Hibernate obviamente ele nao faz, e ja disseram que nao vao fazer e sao contra isso. Fechar a session depois é trivial por um interceptador ou Filter, como voce mesmo disse.

[quote=J2Alex]
A grande vantagem é que o hibernate te permite configurar o padrão e mudar esse padrão em tempo de execução de acordo com a sua necessidade. O que pode ser mais flexível que isso?[/quote]

Perfeito

Bem, usar LazyLoading ou não depende da situação.

Aqui no meu trabalho, havia uma tela cuja busca demorava cerca de 15 minutos! Fui analisar e vi que ele buscava todas as dependências dos objetos (e eram muitas!!!), estava com lazy=“false”.

Coloquei esse relacionamento como lazy=“true”, e nas outras buscas que eram mais pontuais trouxe as dependências na mão (Hibernate.initialize()).

No fim das contas, a busca gastava menos de 1 minuto! :slight_smile:

bom, concordo que lazy ou não depende do caso …
mas imagine a seguinte situação:
os objetos persistentes possuem lógica de negócio …
e dependendo da operação, eles precisam navegar em suas coleções, e referencias para seus pais …
mas isto só acontece em uma ou duas operações destes objetos …
e estas operações não são chamadas sempre …
mas estas mesmas operações são realizadas em cascata …

neste caso, eu consigo enxergar apenas 3 soluções possíveis:
:arrow: Carregar o banco de dados todo na memória pois se eu chamar um destes métodos os dados para qualquer uma das hierarquias devem estar disponíveis
:arrow: Fazer com que os proprios objetos persistidos saibam acessar o banco de dados, o que acarretaria problemas e aumentaria o acoplamento da regra de negócio com a persistencia
:arrow: Utilizar lazy loading automático (como o do hibernate)
:arrow: partir para uma arquitetura onde os objetos persistidos sejam apenas value objects sem lógica nenhuma

lógico, este é um caso apenas, mas neste caso, o lazy loading é uma excelente opção, e praticamente obrigatório na minha opinião, visto que a primeira opção é inviável, a segunda bastante indesejável, e a ultima não me agrada nem um pouquinho …

Excelente colacação Urubatan. Obrigado!

Por que isso não te agrada muito:

Bom, não sou exatamente eloqüente o suficiente para detalhar o por que eu não gosto desta abordagem …
um possível argumento é que utilizar entidades persistentes apenas como structs para carregar dados é contra a idéia de orientação a objetos …
um dos melhores textos sobre isto que ja encontrei é o do wiki do Philip Calçado

Java não é uma linguagem que facilita muito um “modelo rico”, mas eu prefiro não utilizar também um “modelo anêmico”, faço algo que se aproxima ao meio termo …
mantendo dentro dos objetos toda a lógica relacionada aos próprios dados …
por exemplo, quando eu adiciono um objeto a uma coleção, o próprio objeto sabe se tem que alterar alguma propriedade daquele filho, ou de algum outro objeto relacionado a ele …
se tem que fazer alguma validação e não permitir que seja adicionado aquele objeto a coleção …

e para isto o objeto precisa ter acesso aos filhos, mas apenas se isto for necessário (caso contrario eu precisaria de todos os dados em memória)

eu ja implementei sistemas utilizando modelos anêmicos …
mas seguindo este mesmo exemplo de adicionar um objeto a uma coleção com validações …

eu não poderia ter um metodo “addItem” na classe nota fiscal, pois esta possivelmente não teria acesso a todos os itens ja existentes para verificar se este objeto ja estava na coleção, se precisaria atualizar algum dado, …
eu precisaria ter um NotaFiscalManager da vida, que teria um metodo “addItem” recebendo o item e a nota fiscal como parametro o que pelo menos na minha opinião ficaria feio …

lógico, este exemplo que eu apresentei é simples o suficiente para ser implementado de outras formas, mas acho que ja esclarece um pouco o meu ponto de vista :smiley:

[quote=urubatan]Bom, não sou exatamente eloqüente o suficiente para detalhar o por que eu não gosto desta abordagem …
um possível argumento é que utilizar entidades persistentes apenas como structs para carregar dados é contra a idéia de orientação a objetos …
um dos melhores textos sobre isto que ja encontrei é o do wiki do Philip Calçado
[/quote]

Aqui, o cv tem um resumo bem pragmático desse artigo :stuck_out_tongue: :

Ola,

Boa discussao!

Eu particularmente não gosto deste modelo de ter na action do framework esse tipo de regra. O codigo parece ficar proceduralzao, e o como o Paulo ja disse if’s verificando se um objeto esta null e precisa ser carregado é um tanto quanto feio.

Na verdade essa solucacao da action parece muito com o que muita gente faz quando tem lazy=true e ta tendo problema de LazyInitialization. Usam o famoso Hibernat.initialize().

Um outro ponto importante, estes tempo eu tava dando consultoria num sistema que estava todo montado com lazy=false. O tamanho das queries chegavam a ter mais de 32 mil caracteres. A performance nem preciso comentar.

]['s

Sobre o domínio pobre, além dos motivos citados nos artigos linkados pelo Urubatan, tem a situaçõa onde nós estamos em 2007 utilizando uma técnica que fazia sentido em 2000 e já foi vencida há quatro anos, pelo menos.

Fazer lazy loading ‘na mao’ com JDBC é inviável. Ainda que você disponha de estruturas de dados burras e não objetos na sua aplicação vai acabar com um DAO com métodos do tipo:

carregaUsuarioComGrupo(String login)
carregaUsuarioSemGrupo(String login)
carregaGrupoAPartirDoUsuario(Usuario u)
carregausuariosDoGrupo(Grupo g)
carregausuarioComPreferenciaseComGrupo(String login)

Praticamente um ou mais métodos por caso de uso. Eu já lidei com isso algumas vezes e vivi muitoe ste drama.

Fazer lazy loading na mão é inviável mesmo. Falei em ao invés de deixar que o lazy loading aconteça automaticamente, vc controlaria quando e onde ele aconteceria, ou seja, vc saberia quando e onde as dependencias são realmente necessários e faria o lazy loading carregando as dependencias na mão através do framework de ORM em questão. No caso do hibernate já vi algumas pessoas sugerirem que vc force o lazy loading antes de dar um forward para a página.

Entendo porém que se vc tem um grafo de objetos de mais de 3 níveis seria muito tedioso fazer na mão o carregamento das dependencias. Não tenho dados estatísticos mas acredito que grafos de objetos de mais de 3 níveis são exceção. Mas entendo que existem…

Quanto ao domínio pobre eu concordo. Sö queria ouvir, a título de aprendizado mesmo, argumentos técnicos sobre o porque isso é mais recomendável.

[quote=saoj]Fazer lazy loading na mão é inviável mesmo. Falei em ao invés de deixar que o lazy loading aconteça automaticamente, vc controlaria quando e onde ele aconteceria, ou seja, vc saberia quando e onde as dependencias são realmente necessários e faria o lazy loading carregando as dependencias na mão através do framework de ORM em questão. No caso do hibernate já vi algumas pessoas sugerirem que vc force o lazy loading antes de dar um forward para a página.
[/quote]

Uh? Como eu poderia saber onde elas são necessárias sem quebrar Camadas?

Olá,

Sera que nos dias de hoje o custo de um proxy é tao alto assim para termos que nos preocupar com isso?

Sinceramente. Acho que nao.

]['s

A resposta é simples: não. E acho que não preciso nem de dados estatísticos pra “provar” isso, acho que qualquer um que já tenha utilizado(e utilizado corretamente) o Hibernate, sabe que o “custo” dos proxys são irrisórios dados os benefícios e facilidades que eles trazem.

Bom, aqui temos uma confusão de conceitos. Lazy loading é justamente pra livrar o programador de ter que ficar controlando o que ele precisa e quando precisa(por isso, lazy). Ele simplesmente solicita e caso não esteja lá ainda, o proxy carrega. Preguiçoso = faz quando realmente precisa. O que você está sugerindo não é nem Lazy nem Eager. Simplesmente é obrigar o desenvolvedor a fazer um “manual fetch”.
Sobre as pessoas que sugerirem carregar antes de mandar pra view, eu vejo isso como um grande problema. Pensem comigo:

  • tenho uma tela que lista cliente e pedidos. Como tenho que listar os pedidos, vou chamar o initialize em pedidos.
  • tenho agora uma tela que lista o cliente e suas compras do passado.

E aqui começam os problemas. Crio outro método para carregar as informações que preciso, ou simplesmente adiciono outro initialize no meu método que já estava criado para a listagem de pedidos? Nos dois aproachs tenho problemas:

  • no primeiro, vou começar a criar métodos para cada listagenzinha que tenho que fazer (coisa que poderia ser evitada com um lazy loading “de verdade”).
  • no segundo, para as duas “telas” estarei carregando informações que não são necessárias para aquela tela. Quando todo mundo resolver que um initialize a mais naquele primeiro método resolveria o problema da tela que ele está criando, teremos um cenário parecido com que o Fábio relatou. Um grafo enorme sendo carregado sem necessidade.

Bom, se estamos construindo estruturas de dados (porque uma classe que só tem atributos, gets e sets, pra mim não difere em nada das estruturas que faziamos em pascal - ok, com a diferença que temos eles encapsulados) acho que não estamos fazendo algo nem perto de OO, certo? :wink:

Encapsulados onde, no Pascal, né? :mrgreen: