Tornando uma história longa sucinta, o padrão arquitetural usado na empresa que trabalho é de VOs, BOs e DAOs (sim, eu sei…), onde cada tabela do banco reflete 1:1 na quantidade de pacotes que temos, ou seja, um VO pra cada tabela, cada campo é uma coluna.
Quando é necessário trazer dados relacionados entre muitas tabelas rapidamente e de uma vez (evitando milhões de transações se usarmos o estilo acima), usamos uma query específica, e uma VO específica(PageBean é nome dela?) para guardar os dados dessas queries. Nada é gerado dinamicamente, tudo é estático, zero frameworks usados.
Isso acaba com que cada listagem que fazemos é query diferente e possivelmente uma VO diferente(se der somente adicionamos mais campos à ela p/ cobrir as nossas necessidades).
Até que a primeira parte que citei no primeiro parágrafo é relativamente limpa, mas este misto de informações que as nossas listagens trazem está fugindo ao controle, muito deselangante, não chegando a ser um POG, mas passa raspando.
Enfim, alguém tem alguma sugestão, alguma idéia, até comentários de “jogue isso fora” são válidos (mas sem apresentar a solução dificilmente serão seguidos).
O que vocês usam em seus sistemas para mostrar dados talulares relacionados, mas ainda sim distintos?
Me baseando na sua descrição se você substituir seus "VO"s por HashMaps você não vai perder nada. Por quê? Porque você Não está usando nem objetos nem estruturas de dados, ao que aprece são apenas agrupamentos de valores que vêm do banco de dados.
Este problema foi solucionado há muitos anos com várias técnicas, dentre elas proramação estruturada. Se você fizer dos seus sacos de dados esruturas de dados vai poder as reutilizar entre chamadas. Se você quiser evoluir ainda mais um pouquinho no tempo misture as estruturas de dados com as funcões (saindo do famoso BO/LO/VO) e bem vindo ao mundo da Orientação a Objetos.
Resumindo: não crie um objeto por funcionalidade. Crie um grupo de objetos que modela seu domínio e faça Mapeamento Objeto-Relacional para transformar seus objetos em tabelas e consultas e vice-versa.
Bom dia pessoal,
Este é um assunto que também me tira o sono as vezes.
Pcalcado, você poderia clarear um pouco mais a sua idéia? Seria pedir demais um exemplo sobre isso?
A única idéia que sempre tenho para este tipo de situação é criar um objeto que faça o mapeamento de meu select para ter todos os campos em um único objeto. E pra ser sincero acho isso meio ruim, mas nunca consegui chegar em uma resolução melhor.
Concordo com o Phillip. Se vc optar por Mapeamento Objeto-Relacional e APÓS APLICAR TODAS AS MELHORES PRATICAS INDICADAS neste tipo de tecnologia que vc encontrar e ainda assim perceber lentidão nos resultados, considere utilizar views mapeadas. Indico views como último recurso em termos de performance porque, apesar de obter maior velocidade na construção do resultado, vc ainda continuará a utilizar objetos do tipo "VO".
Hehe, só preciso de um post do Phillip pra perceber que toda uma classe de objetos do sistema pode ser eliminada (ou pelo menos não ser utilizada como está sendo)… pode parecer óbvio, mas eu nem pensei em usar HashMaps, garanto que muitos outros não sabem, e isso digno de um artigo por si só.
Muito obrigado!
Já fiz um teste com listas de hashmaps, funciona perfeitamente. Entretanto ainda preciso estudar mais sobre como o JSTL trabalha com um único Hashmap, fiz um:
<c:set var="pessoa" value="${requestScope.mapa}"/>
<c:out value="${pessoa.nome}"/>
<c:out value="${pessoa.idade}"/>mas o teste não deu certo. suspira
[quote=lelodois]Pelo que entendi idéia é partirmos para um framework de objeto com o banco de dados, tais tecnologias como hibernate, toplink, entre outros…
Seria esta a idéia??
[/quote]
Se eu fosse buscar os objetos do banco, sim. Entretanto, os objetos já voltaram da camada de persistência, agora o serviço é entregá-los na apresentação. A pergunta foi “como fazer isso sem criar mil classes?”.
Frameworks de persistência estão duas refatorações à frente, e antes disso é preciso fazer um ORM melhor.
fantomas: O que é uma View Mapeada? Não consigo achar no Google sobre isso.
Oi Bruno (para quem não sabe, Bruno é o nome verdadeiro de renrutal).
Bem, na época que eu tinha trabalhado exatamente na mesma empresa que você está hoje , eu tinha feito um esquema lá para acabar com esse negócio de VOs específicas para as querys. Basicamente a merda toda começa na idiotice de VOs refletirem 1:1 o BD. O que eu tinha feito na época, é que ao invés de refletirem o DB, elas “puxassem” alguns campos das tabelas relacionadas (aquelas das quais há uma chave estrangeira). Isso não só eliminou quase todos esses VOs artificiais, como simplificou bastante a implementação, pois foi eliminada a necessidade de ficar navegando entre tabelas. O código ficou menor, mais limpo e menos propenso a bugs.
Ainda ficou o tradicional (e idiótico) esquema VO e BO (ou BE como chamam aí). Mas isso é outra coisa. Refatorar isso é uma tarefa bem mais complicada (ainda mais que essa empresa, mantinha grande resistência em migrar para o java 5). Na época eu não o via o esquema VO/BO como algo ruim, mesmo porque havia coisas bem piores chamando muito mais a atenção.
Outra coisa que fiz foi fazer TODAS as sqls SEMPRE trazerem todos os campos da VO (incluindo o de outras tabelas). Apesar disso pode trazer alguns campos a mais desnecessários, facilitou bastante na manutenção e acabou com bugs causados por causa de campos que não eram trazidos.
Outra saída seria colocar VOs que usam outras VOs por composição. Mas depois que eu saí daí dessa empresa onde você está, eu caí em um projeto que usava isso e digo que é uma idéia muito pior do que VOs refletindo o BD 1:1. Embora puristas de OO provavelmente discordarão, ou não entenderão porque, basicamente é porque fica difícil saber o que deve ou não ser carregado, o código fica muito mais propenso a NullPointerExceptions e fica cheio de gets encadeados na forma voQualquer.getIsso().getAquilo().getAqueleOutro().getNaoSeiOQue().
Eu havia criado taglibs que eram façades para os métodos de consulta dos objetos de negócio (BEs nos sistemas mais novos daí). Isso reduziu drasticamente a necessidade de os servlets fazerem consultas dezenas de consultas “burras” e simplesmente socarem tudo no request.
Basicamente, se a JSP tem o código da Equipe, por meio da taglib ela obtém o VO da equipe. Isso simplificou bastante o código nos servlets, uma vez que reduz a necessidade dos servlets popular os dados para a JSP.
Sim, simplificou em muito qualquer tipo de operação, sempre os dados que você quiser estarão disponíveis. Infelizmente isso causa outro problema: Escalabilidade.
Mesmo para uma lista simplérrima que mostra tabela com 5-6 colunas de dados, todos os 51 atributos do objeto estão sendo preenchidos. Multiplique isso por 28000 objetos gordos desde meados de outubro do ano passado, uns 150 por dia(ou mais, se contar só os dias úteis). A consulta mais abrangente trás uns 20 mil desses, jogando por baixo. Sem paginação.
Um dia gostaria de fazer uma análise dos picos de memória desse monstro (isso se eu voltar p/ o projeto). Pelo menos já ouvi estórias de horror piores.
Sim, simplificou em muito qualquer tipo de operação, sempre os dados que você quiser estarão disponíveis. Infelizmente isso causa outro problema: Escalabilidade.
Mesmo para uma lista simplérrima que mostra tabela com 5-6 colunas de dados, todos os 51 atributos do objeto estão sendo preenchidos. Multiplique isso por 28000 objetos gordos desde meados de outubro do ano passado, uns 150 por dia(ou mais, se contar só os dias úteis). A consulta mais abrangente trás uns 20 mil desses, jogando por baixo. Sem paginação.
Um dia gostaria de fazer uma análise dos picos de memória desse monstro (isso se eu voltar p/ o projeto). Pelo menos já ouvi estórias de horror piores. ;)[/quote]
Bem, o negócio também não é 8 ou 80. No geral as visões do sistema eram em cima de um tipo de dados. E as VOs deveriam trazer os dados importantes (SÓ OS IMPORTANTES) das tabelas pais. Aqui não há receita de bolo, tem que ir no bom senso.
E também, se realmente o que você está trazendo não se encaixar bem em uma VO existente, crie uma nova (aka PageBean). As PageBeans tem o seu propósito válido (ao menos em um mundo orientado a VO), o problema era o abuso delas, onde para qualquer coisa tinha uma PageBean.
Me conte mais (em PVT se achar o caso) sobre o que tem acontecido com os códigos por aí depois que saí. Tenho curiosidade em saber.
[quote=victorwss]
Outra saída seria colocar VOs que usam outras VOs por composição. Mas depois que eu saí daí dessa empresa onde você está, eu caí em um projeto que usava isso e digo que é uma idéia muito pior do que VOs refletindo o BD 1:1. Embora puristas de OO provavelmente discordarão, ou não entenderão porque, basicamente é porque fica difícil saber o que deve ou não ser carregado, o código fica muito mais propenso a NullPointerExceptions e fica cheio de gets encadeados na forma voQualquer.getIsso().getAquilo().getAqueleOutro().getNaoSeiOQue().[/quote]
Iso é um problema comum de ORM. A melhor soluçao é lazy loading, mas se iso não é possível você precisa amarrar o que é retornado em cada consulta com um caso de uso. Para eiar uma explosão de métodos do tipo retornaEquipeComTecnicoMasSemMembros(), retornaEquipeApenasComMembrosESeusNomes() e etc uma saída é utilizar QueryObjects que são passados ao DAO.
De qualquer forma, ainda que não houvesse qualquer outro motivo para uar um engine de ORM lazy loading seria motivo suficiente.
Bom, chamando os bois pelos nomes corretos vc esta tentando diminuir a necessidade de composição criando um objeto que não traduz o dominio mas traduz o seu modelo de relatorio ou de pesquisa.
Esse padrão, conhecido como TO ou DTO ou VO ( este agora em desuso) tem um irmão chamado HashDTO.
O HashDTO é ideia de utilizar uma interface do tipo put/get como num Map mas em que as chaves são strings e que funciona assim
Isso elimina a necessidade de um objeto especifico por query mas obriga a saber os nomes dos campos e não pode mais usar get e set.
Ai vc utiliza um outro padrão chamado Proxy. Na realidade DinamicProxy.
Este padrão funciona assim: vc cria uma interface com os get/set. Se cria uma estrutrua para vincular essas interface ao hashdto em que o nome do campo é retirado do nome do método assim
interface XTPO {
getNome()
getCodigo()
}
// não precisa de set porque é só para leitura
public class HashDTOHandler implements InvocationHandler{
HashDTO dto;
public HashDTOHandler (HashDTO dto ){
this.dto = dto;
}
public Object invoke(Object proxy, Method method, Object[] args) {
return dto.get(method.getName().substring(4));
}
}
XPTO objeto = (XPTO)java.lang.reflect.Proxy.getProxyClass(XPTO.class.getClassLoader(), new Class[]{XPTO}, new HashDTOHandler(hdto) ); // um generics vai bem aqui...
Então suas queries podem converter os resultSets em HashDTO e o mecanismo linkados com as interfaces.
Porstanto vc não precisa implementar get/set nem classes diferentes. Precisa de interfaces diferentes se quiser strong typing, ou usa o HashDTO diretamente. O problema dele é que não segue o padrão javabeans de get/set então as bibliotecas padrão, como tags não podem ser usadas com eles. Com o uso da interface isso não é mais problema.
Dito isso um ultimo passo
Se esses objetos são para ser usados em queries é provavel que vc esteja interessado numa lista desses objetos e não em 1 só. Nesse caso vc pode usar um outro padrão. O fastlane.
A ideia é que vc não traduz o ResultSet para esses objetos e os colona numa lista e anda por ai com a lista
Vc colocar o resultSet num objeto que tem um iterador (pode ser um List mas não precisa) e depois vc cria o objeto a cada chamada de it.next(). Desta forma vc só tem um objeto em cada chamada e não milhares de objetos. Assim diminui a necessidade de memoria e aumenta a performance. Claro que isto só funciona em aplicações web ou standalone onde o resultset possa permanecer aberto até querermos.
Foram algumas ideias de como vc pode ter menos trabalho e ganhar eficiência
[quote=pcalcado]Iso é um problema comum de ORM. A melhor soluçao é lazy loading, mas se iso não é possível você precisa amarrar o que é retornado em cada consulta com um caso de uso. Para eiar uma explosão de métodos do tipo retornaEquipeComTecnicoMasSemMembros(), retornaEquipeApenasComMembrosESeusNomes() e etc uma saída é utilizar QueryObjects que são passados ao DAO.
De qualquer forma, ainda que não houvesse qualquer outro motivo para uar um engine de ORM lazy loading seria motivo suficiente.[/quote]
Nem me fale. Esses métodos que fazem “aquela consulta anterior, com só esta variável a mais” me dão dor de cabeça.
Já há vários dias estou pensando em perguntar aqui “Como fazer uma consulta genérica”, ou até perguntar se alguém conhece uma engine de consultas. Query Objects são complexos demais para fazerem na mão, o trabalho equivale ao de fazer um interpretador para uma linguagem. Por isso a preferência a usar o Criteria API do Hibernate.
Por acaso ele tem algum analisador embutido para verificar a sequência mais eficiente de joins de tabelas?
[quote=renrutal][quote=pcalcado]Iso é um problema comum de ORM. A melhor soluçao é lazy loading, mas se iso não é possível você precisa amarrar o que é retornado em cada consulta com um caso de uso. Para eiar uma explosão de métodos do tipo retornaEquipeComTecnicoMasSemMembros(), retornaEquipeApenasComMembrosESeusNomes() e etc uma saída é utilizar QueryObjects que são passados ao DAO.
De qualquer forma, ainda que não houvesse qualquer outro motivo para uar um engine de ORM lazy loading seria motivo suficiente.[/quote]
Nem me fale. Esses métodos que fazem “aquela consulta anterior, com só esta variável a mais” me dão dor de cabeça.
Já há vários dias estou pensando em perguntar aqui “Como fazer uma consulta genérica”, ou até perguntar se alguém conhece uma engine de consultas. Query Objects são complexos demais para fazerem na mão, o trabalho equivale ao de fazer um interpretador para uma linguagem. Por isso a preferência a usar o Criteria API do Hibernate.
Por acaso ele tem algum analisador embutido para verificar a sequência mais eficiente de joins de tabelas?[/quote]
Um dos objetivos daquela coisa de sempre puxar todos campos pertinentes também visava acabar com esse problema de métodos quase idênticos que traziam um campo a mais aqui e um a menos ali. Promovia alguma reusabilidade no código. Mas, como você já sabe, isso tem um preço.
Query objects não são tão difíceis assim não. Para fazê-los a mão pode começar a olhar acerca de ResultSetMetadata. Mas o resultado é um saco de propriedades (aka Map<String, Object> (isso se você puder usar generics)).
Quanto a seqüência de joins, que eu saiba o Oracle (que é o BD que vocês usam) tenta otimizar isso (mas também não faz milagre, tem que ajudar ele).
[quote=sergiotaborda]Se esses objetos são para ser usados em queries é provavel que vc esteja interessado numa lista desses objetos e não em 1 só. Nesse caso vc pode usar um outro padrão. O fastlane.
A ideia é que vc não traduz o ResultSet para esses objetos e os colona numa lista e anda por ai com a lista
Vc colocar o resultSet num objeto que tem um iterador (pode ser um List mas não precisa) e depois vc cria o objeto a cada chamada de it.next(). Desta forma vc só tem um objeto em cada chamada e não milhares de objetos. Assim diminui a necessidade de memoria e aumenta a performance. Claro que isto só funciona em aplicações web ou standalone onde o resultset possa permanecer aberto até querermos.[/quote]
Este aqui fiquei pensando um pouco melhor hoje de manhã (tomando banho semi-acordado e minha mente vaga p/ fastlanes, muito estranho :?).
Em Java EE, para isso funcionar com o c:forEach do JSTL, esse FastlaneIterator teria que implementado dentro de uma Collection, certo? E praticamente o único método implementado seria o iterator.
A minha dúvida é quanto o fechamento da conexão, implementando rs.close no finalize do iterator, suponho que a tag liberaria o ResultSet, mas isso fecharia a conexão com o BD? O recurso seria liberado?
A dúvida é porque estamos atravessando camadas com este recurso, mas normalmente na camada de persistência abrimos a Connection, PreparedStatement, e ResultSet, mas também as fechamos com rs.close, ps.close, conn.close, nesta ordem. Mesmo que frameworks ou a própria aplicação abstraiam esses conceitos, fechar sempre é a operação comum entre eles.
[quote=renrutal][quote=sergiotaborda]Se esses objetos são para ser usados em queries é provavel que vc esteja interessado numa lista desses objetos e não em 1 só. Nesse caso vc pode usar um outro padrão. O fastlane.
A ideia é que vc não traduz o ResultSet para esses objetos e os colona numa lista e anda por ai com a lista
Vc colocar o resultSet num objeto que tem um iterador (pode ser um List mas não precisa) e depois vc cria o objeto a cada chamada de it.next(). Desta forma vc só tem um objeto em cada chamada e não milhares de objetos. Assim diminui a necessidade de memoria e aumenta a performance. Claro que isto só funciona em aplicações web ou standalone onde o resultset possa permanecer aberto até querermos.[/quote]
Em Java EE, para isso funcionar com o c:forEach do JSTL, esse FastlaneIterator teria que implementado dentro de uma Collection, certo? E praticamente o único método implementado seria o iterator.
[/quote]
Normalmente vc usaria um coleção como um ArrayList, por exemplo. O ponto é que o forEach só precisa do iterador. Se isso é verdade eu posso fornecer uma implementação qualquer da interface Collection. Em particular uma que utilize o padrão Fastlane no seu iterador. Todos os métodos teriam que ser implementados, mas como implementações assim :
public int size(){
throw new UnsupportedOperationException();
}
Dá exception se o tag library ou qualquer um tentar utilizar os métodos que não seja iterator(). Em opção pdoe ser usar a interface Iterable que só tem esse método.
O resultSet seria fechado no finalize sim. Ou do iterador ele mesmo ou da colection. A dirença é que se fechar no iterador não pode repetir a invocação collection.iterator(). Enquanto que se for a coleção a reter o ResultSet vc pode reaproveitá-la depois dando um reset no ResutSet ( se o ResultSet foi criado para isso, é claro).
Sim, o recurso seria liberado, mas não fecha a coneção com o banco. Isso só e feito se vc der um close na connection. Mas isso é simples, encapsule a conecção ela mesma junto com o ResultSet.
[quote]
A dúvida é porque estamos atravessando camadas com este recurso, mas normalmente na camada de persistência abrimos a Connection, PreparedStatement, e ResultSet, mas também as fechamos com rs.close, ps.close, conn.close, nesta ordem. Mesmo que frameworks ou a própria aplicação abstraiam esses conceitos, fechar sempre é a operação comum entre eles.[/quote]
Essa é a diferença para o Fastlane. Como eu digo no blog entender o padrão é facil, implementá-lo é que é o desafio. No caso particupar de utilizar Fastlane vc não pode fechar a conection até que os dado sejam realmente lidos. O padrão adia a leitura dos dados e força que eles sejam lidos em sequencia para que não haja acumulação de objetos na memoria. Isso podemos entender como uma otimização, mas o padrão como o todo é um padrão de otimização.
Desculpem a demora em escrever...
Respondendo sua pergunta sobre "view mapeada", eu estava fazendo uma referencia a estratégia de construir uma view no banco de dados (create view .....) e depois montar um mapeamento (xml ou annotations) para essa view e utiliza-la como uma "entidade" normal. O objetivo com essa idéia (simples) é obter performance no resultado em estruturas mapeadas de grande volume.
Mas pelo que entendi da discussão, vc está optando por NÃO utilizar mapeamentos, certo?
Nada contra mapeamentos xml (a menos que fazer um p/ cada caso ;), mas o problema não é ir encontrar os dados, pelo menos não agora.
Quanto as Views, primeiro esse tipo de caso não justifica o uso delas, segundo, só depois de coações à mão armada que o pessoa do banco permite algo do tipo