Hibernate 2.1.7 // Collections!

Oi pessoal,

Esse Hibernate vai me endoidar! :?

Vou explicar direitinho o que preciso e o que está acontecendo! (Na verdade estou tirando vários campos para simplificar o entendimento por parte de vocês)

Tenho um sistema de retaguarda de uma aplicação comercial. Uma das telas desse sistema é o de CONSULTA DE PRODUTOS. A tela possui um JTextField onde o usuário digita a palavra-chave e possui um JComboBox onde seleciona-se o critério no qual se está fazendo a busca, podendo ser:

  1. Código interno
  2. Código de barras
  3. Descrição

Depois disso, a tela possui um JTable com 4 colunas (as quais correspondem as 3 opções acima e incluem ainda a coluna PREÇO).

Ok… Até aí tudo bem… Tenho meus .HBM.XML e beans criados (abaixo):

public class Produto {

    // Campos existentes na tabela
    private Integer       idProduto;
    private String        descricao;
    private String        descricaoResumida;
    private Marca         marca;
    private Secao         secao;
    private Float         vendaMedida;
    private UnidadeMedida vendaUnidadeMedida;
    private Float         preco;
    private Integer       estoque;
    private Float         vasilhameMedida;
    private UnidadeMedida vasilhameUnidadeMedida;
    private Tributo       tributo;

    // Relacionamentos um-para-muitos
    private Set           codigoBarras;
    ...
public class ProdutoCodigoBarras {

    private String  codigoBarras;
    private Produto produto;
    ...

Percebam então que, independentemente de qual critério eu selecionar na consulta, as minhas QUERIES deverão sempre incluir um JOIN, para que eu possa exibir o código de barras dos produtos.

Percebam também que, um mesmo produto pode ter 0 ou mais (1, 2, 3, …, n) códigos de barras. Sim, 0 porque podem existir produtos que não possuam código de barras.

No BD eu tenho duas tabelas relacionadas a isso: são a tb_produto e tb_produto_codigo_barras. A primeira possui os campos ID, descrição, preço, etc. A segunda possui apenas o campo p/ o código de barras e outro para o produto (para dizer qual é o produto que possui este código de barras).

Em SQL, uma consulta típica para listar os produtos que iniciem pelo nome SHAMPOO seria:

SELECT p.id, cb.pcbr_codigo_barras, p.prod_descricao, p.prod_preco FROM tb_produto p
LEFT OUTER JOIN tb_produto_codigo_barras cb ON cb.pcbr_produto = p.id 
WHERE UPPER(p.prod_descricao) LIKE 'SHAMPOO%'

  id  | pcbr_codigo_barras |                  prod_descricao                   | prod_preco 
------+--------------------+---------------------------------------------------+------------
   10 | 7891037004276      | Shampoo Seda Dvs.                                 |       4.50
   10 | 7891037002586      | Shampoo Seda Dvs.                                 |       4.50
   10 | 7891037000308      | Shampoo Seda Dvs.                                 |       4.50
  350 |                    | Shampoo Revitalizante Permanente Afro 340ml       |       4.99
  351 | 7896000706478      | Shampoo Fell Free 350ml                           |       4.20
  374 | 7896000705013      | Shampoo Manteiga de Karité Não Engordurante 340ml |       4.99

Percebam então que o produto de código 10 (Shampoo Seda Dvs.) possui 3 diferentes códigos de barras cadastrados. Já o produto 350 (Shampoo Revitalizante) não possui código de barras cadastrado. Os 2 últimos não possuem nada de especial.

Minha consulta HQL para o caso acima é:

result = session.find( "select distinct p "
    + "from tisc.multuspdv.beans.Produto as p "
    + "left outer join fetch p.codigoBarras as cb "
    + "where upper(p.descricao) like ? order by p.id", nome.toUpperCase()
    + "%", Hibernate.STRING );

Bom, o problema começa quando minha consulta acima deveria retornar 4 Produtos, mas ele me retorna 6. Tudo bem, eu vi na documentação do Hibernate (péssima, por sinal) que isso é normal e que devemos ter código Java para identificar objetos iguais/repetidos. E foi por isso que no meu código eu tive de colocar:

for ( int i = 0; i < result.size(); i++ ) {

    Produto produto = ( Produto )result.get( i );

    if ( i > 0 && produto.hashCode() == ( ( Produto )result.get( i - 1 ) ).hashCode() )
        continue;

    ...

Funciona a contento! Caso o objeto seja igual ao último, ele pula para a próxima iteração do laço FOR, caso contrário, ele continua o código até chegar em um laço WHILE (ver abaixo) que passa por todos os códigos de barras que existam para o mesmo produto:

...
Iterator it = produto.getCodigoBarras().iterator();

// Enquanto existir códigos de barras diferentes
while ( it.hasNext() ) {

    totalItems++;

    dtm.addRow( new Object[] { produto.getIdProduto(),
            ( ( ProdutoCodigoBarras )it.next() ).getCodigoBarras(),
            produto.getDescricao(), nf.format( produto.getPreco() ) } );
}

Até então eu já estava achando não tão elegante meu código, mas aí tudo piorou quando eu percebi que os produtos que não tem código de barras cadastrados não estavam sendo listados na minha JTable. Isso acontece pelo fato de que os objetos (Produtos) retornados do Hibernate que não possuem código de barras simplesmente possuem um SET (ou Lista ou Bag ou qualquer outra coisa :smiley: ) vazio. Dessa forma os comandos internos ao laço WHILE nunca são executados e eu “perco” um produto (percebam que o ADDROW está lá dentro). Para resolver isso, insiro mais uma obscenidade em meu código antes da chamada iterator()! :smiley:

if ( produto.getCodigoBarras().isEmpty() )
    produto.getCodigoBarras().add( new ProdutoCodigoBarras() );

Funciona a contento… Agora meu JTable exibe TODOS meus produtos, independetemente de cada um deles ter ou não código de barras, ou de ter vários.

A grande questão desse tópico é o seguinte:

  • Trabalhar com o Hibernate é desse forma ou existem outras maneiras que não necessitam de todo esse trabalho sujo para “controlar” o resultado a ser exibido? Não existe maneira de ser melhor? Ou sou eu que sou um desenvolvedor falido? :slight_smile:

Eu já rodei a Internet atrás de soluções EFICIENTES para os problemas acima e simplesmente não encontrei NADA.

Aguardo a resposta de um algum guru! :slight_smile:

Paulo Oliveira

acho que ta meio estranho o jeito que voce quer que a pesquisa retorne
vc ta pesquisando um Produto, e quer que retorne 3 vezes o mesmo Shampoo só pq tem codigos de barras diferentes, por exemplo…

o Shampoo é 1 só, o fato dele ter 3 codigos de barra nao o transforma em 3 produtos diferentes… voce tem que sobrescrever o hashCode do Produto pra identifciar pela PK do banco, de preferencia…

a forma que eu entendo de fazer esse tipo de busca, é fazer com que essa coleção de codigos de barras sejam “lazy=false”… assim basta voce pesquisa por “from Produto p where p…”, sem join
ai o “Shampoo Seda Dvs” vai aparecer só 1x (se o hashCode estiver certo), o que é o correto, com 3 codigos de barra em sua coleçao, ja montados…
voce itera a coleção e povoa a table… mas no caso de Produtos sem codigo de barras, vai vir um Set vazio mesmo

pensando OO, qdo vc faz essa busca:
result = session.find( "select distinct p "
+ "from tisc.multuspdv.beans.Produto as p "
+ "left outer join fetch p.codigoBarras as cb "
+ “where upper(p.descricao) like ? order by p.id”, nome.toUpperCase()
+ “%”, Hibernate.STRING );

ta percebendo que seu retorno nao é um Produto? o produto de chave 10 é unico, é UM produto somente… se o codigo do produto é a pk dele no banco… e a pesquisa retorna ele 2, 3x repetidos, é pq tem algo errado, o hashCode…
em SQL esta corretissimo essa busca… mas pensando em OO mesmo foge um pouco do paradigma e foge do q o hibernate propoe
vc ta retornando é um “ProdutoTemporario”… cuja identificação é o “codigo do produto” e o “codigo de barras”
1 Produto com 3 codigos de barra vira 3 “ProdutosTemporarios”

mas acho q vc nao fazendo esse join, deixando q a coleção “não-lazy” ja venha pra vc, e vc iterando ela pra montar a table… é a melhro forma

nao sei se entendi muito bem o seu problema, pode ser que nem falei oq vc perguntou, mas respondi oq entendi

Oi Axel,

Sobrescrevi o hashCode()!

public int hashCode() {
    return idProduto.hashCode();
}

E depois retirei do meu laço FOR o código que identificava objetos iguais. Beleza! Funcionou…

O primeiro problema de não usar JOIN e sim uma query HQL como “from Produto as p where p…” é o seguinte: digamos que a palavra-chave da busca é SHAMPOO, isso me retorna 49 produtos. Deste 49, alguns não tem código de barras, outros tem 1 código de barras e outros tem vários códigos de barras. Isso totaliza 168 itens na minha JTable! CORRETÍSSIMO! O problema é que o Hibernate simplesmente faz 49 QUERIES para isso!!! Tudo isso porque essa query HQL não realiza o JOIN, tendo que pegar individualmente os códigos de barras um a um (sim, estou com lazy=“true”).

Essa consulta de 168 itens (49 produtos) com a query anterior (usando JOIN) gerava um resultado instantâneo! Com essa nova query o resultado leva 0.7s! Uma grande perda de desempenho! Para ficar mais evidente esse problema, uma outra consulta que retorna 1097 itens na query anterior era também instantâneo, agora leva nada menos que 6 segundos para preencher minha JTable! :frowning:

Solução para isso?! :?

E com relação ao Set vazio. Tem alguma forma mais elegante sem ser aquele código no momento do preenchimento da JTable que adiciona um código de barras nulo quando o Set está vazio?

Você entendeu direito minha dúvida! Foi isso mesmo!!!

Um abraço,

Paulo Oliveira

td blz… se for pra levar em conta o desempenho, td bem… realmente o hiberate vai montar todos os objetos 1 a 1 dando load separado pra cada um…
com seu join realmente vai ficar bem mais rapido, mas mesmo assim ele vai carrgar todos os objetos CodigoBarra, por causa do lazy…
e tambem quebra com um pouco com a visao OO que o hibernate sugere… mas blz… performace antes de tudo…

voce quer apenas mostrar na table? nao vai mexer com amis nada nesses objetos? voce precias realmente do OBJETO? pq se nao faz uma busca que retorna só oq vc precisa, ai o hibertae nao vai montar o objeto pra voce… e nao vai ter problema com hashCode essas coisas…

faz igual vc fez no sql:
result = session.find( "select p.id as ID, cb.pcbr_codigo_barras as CB, p.prod_descricao as DESCRICAO , p.prod_preco as PRECO " +

  • "from tisc.multuspdv.beans.Produto as p "
  • "left outer join fetch p.codigoBarras as cb "
  • “where upper(p.descricao) like ? order by p.id”, nome.toUpperCase()
  • “%”, Hibernate.STRING );

se for só pra visualização na tabela, ta bom
vc nao vai precisar do objeto todo

hibernate, hibernate, hibernate… to treinando digitar hibernate
pq nao acertei nenhuma vez no reply anterior hahaha

hibernate, hibernate, hibernate…

Axel…

Treina mais um pouco o Hibearte… Ops… HIBERNATE! Aahahah

Seguinte… E se minha busca for pelo código de barras? Ehehe

Sei que isso não pode:

session.find( "from Produto p where p.codigoBarras.codigoBarras like ?", ... );

Já tentei ao contrário também, mas retorna coisas nada a ver:

session.find( "select cb.produto from ProdutoCodigoBarras cb where cb.codigoBarras like ?", ... );

Não sei bem, mas até onde li parece que tem que FILTRAR… Putz!

Alguém sabe?!

Paulo Oliveira

esse primeiro exemplo seu nao pode fazer diretamente isso, por ser colecao, com propriedade normal poderia
mas da pra fazer assim:

"from Produto p left outer join p.codigoBarras cb " +
"where cb.codigoBarras like ?"

o segundo exemplo seu era pra funcionar… ta certo hql
se ta retornando algo errado, é pq deve ter algo erardo no mapeamento

Putz…

Assim fica difícil! Sempre um novo problema! :?

Com esse seu exemplo de código não funciona! Ou melhor, a query é executada mais isso retorna um array de Object (Object[]). Dessa forma eu vou ter de refazer AQUELE meu laço FOR… Ou pior ainda, ter diversos FOR diferentes para cada tipo de consulta de produto que eu quero! :frowning:

A minha consulta em SQL equivale a isso e retorna os seguintes dados:

    select p.id, cb.pcbr_codigo_barras, p.prod_descricao, p.prod_preco
      from tb_produto p
left outer join tb_produto_codigo_barras cb on cb.pcbr_produto = p.id
     where cb.pcbr_codigo_barras like '78946500%' order by p.id

  id  | pcbr_codigo_barras |               prod_descricao                | prod_preco 
------+--------------------+---------------------------------------------+------------
  187 | 7894650067055      | Bloc.Sanit.Pat.Purif.Ap.Dvs.                |       3.99
  189 | 7894650066348      | Gleid T.Frescor Ap.Dvs.                     |       5.50
  190 | 7894650064030      | Gleid Sany Refil Dvs.                       |       2.55
  191 | 7894650067161      | Pato Purific Refil Dvs.                     |       4.25
  193 | 7894650066331      | Gleid T.Frescor Refil Dvs.                  |       4.50
  199 | 7894650020074      | Lustra Moveis Brilhol Dvs.                  |       1.80
  202 | 7894650041369      | Optimun Pronto Uso Dvs.                     |       3.35
  202 | 7894650040331      | Optimun Pronto Uso Dvs.                     |       3.35
  214 | 7894650018217      | Cera Bravo Classic 850ml Dvs.               |       4.99
  222 | 7894650079201      | Pulverizador Baygon                         |       6.70
  228 | 7894650090947      | Neutralizador de Odores Gleid               |       5.45
 1172 | 7894650090923      | Gleid Desodorizador de ambiente 400ml. dvs. |       5.50
 1172 | 7894650090916      | Gleid Desodorizador de ambiente 400ml. dvs. |       5.50
 1172 | 7894650090886      | Gleid Desodorizador de ambiente 400ml. dvs. |       5.50
 1175 | 7894650090954      | Glad Odorizador de ar 400ml                 |       5.45
 1175 | 7894650090893      | Glad Odorizador de ar 400ml                 |       5.45
(16 rows)

Ou seja, 16 registros. Perceba que são 12 produtos diferentes!

Coloquei um “select p” no início. Agora ele retorna instâncias de Produto, mas volta aquele problema de duplicação. Mudei então para “select distinct p” e agora ele retorna corretamente 12 produtos diferentes. Só que as consultas que o Hibernate faz para pegar cada código de barras não estão corretas, e isso faz com que apareçam 19 linhas na minha JTable. O que o Hibernate faz para pegar cada código de barras é:

select codigobarr0_.pcbr_produto as pcbr_pro2___,
codigobarr0_.pcbr_codigo_barras as pcbr_cod1___,
codigobarr0_.pcbr_codigo_barras as pcbr_cod1_0_,
codigobarr0_.pcbr_produto as pcbr_pro2_0_ from
tb_produto_codigo_barras codigobarr0_
where codigobarr0_.pcbr_produto=?

Fazendo com que ele pegue códigos de barras que não me interessam, pois, recapitulando, estou querendo agora pegar produtos que tenham códigos de barra iniciando por 78946500. Ele deveria fazer a consulta acima adicionando um “and codigobarr0_pcbr_codigo_barras like ‘78946500%’” na claúsula WHERE.

Isso é a mesma coisa que acontece quando eu tento pegar os Produtos usando:

result = session.find( "select distinct cb.produto from ProdutoCodigoBarras as cb "
+ "where cb.codigoBarras like ?", codigoBarras + "%", Hibernate.STRING );

Putz… Nunca pensei que fosse essa novela toda com o Hibernate apenas para exibir dados dentro de um JTable que vem de diferentes tabelas. :frowning:

Um abraço,

Paulo Oliveira

[quote=paulo.oliveira]Putz…

Assim fica difícil! Sempre um novo problema! :?

Com esse seu exemplo de código não funciona! Ou melhor, a query é executada mais isso retorna um array de Object (Object[]).

Um abraço,

Paulo Oliveira[/quote]

é exatamente isso, vai vir somente os itens q vc especificou, nao vai montar o objeto… é só vc fazer um for até o size do array, fazendo cast pra cada um dos tipos q vc pesquisou

E aí Paulo, seguinte, tenho duas dúvidas quanto ao seu problema:

  1. Vc quer que o produto repita caso ele possua mais de um código de barras?
  2. Vc apresenta isso em um JTable, certo? Pq vc não cria um TableModel para resolver seu problema de apresentação??