DClick

Hibernate Criteria com subquery


É realmente impressionante como o Hibernate pode fazer consultas sql usando Criteria. Ontem precisei fazer uma consulta e cheguei à conclusão que seria necessário o uso de subquery.

Primeiro, fiz a consulta por um query console do meu banco para ter certeza. E um pouco descrente de que Criteria iria resolver o meu problema 100%, procurei por subquery na documetação do Hibernate.

Encontrei a solução e, para a minha surpresa, a consulta sql gerada foi exatamente a que eu havia feito no query console do meu banco de dados.

Então, vamos ao exemplo!

Suponhamos que temos registros de frutos e que, cada fruto contém uma lista de vitaminas:


Fruit: Pineapple
--Vitamins: [Vitamin C, Vitamin B6]
Fruit: Orange
--Vitamins: [Vitamin C, Vitamin B6]
Fruit: Banana
--Vitamins: [Vitamin C, Vitamin A]

Nossas classes, já mapeadas com annotations, ficariam assim:

Fruit.java:

JAVA:
  1. @SequenceGenerator(name = "sq_fruit", sequenceName = "sq_fruit")
  2. public class Fruit {
  3.  
  4.     @Id
  5.     @GeneratedValue(strategy = GenerationType.AUTO, generator = "sq_fruit")
  6.     private Integer id;
  7.     private String name;
  8.     @ManyToMany(cascade = CascadeType.ALL)
  9.     @ForeignKey(name = "fk_Fuit", inverseName = "fk_Vitamin")
  10.     @JoinTable(    name = "Fuits_Vitamins",
  11.             joinColumns = @JoinColumn(name = "Fruit_id"),
  12.             inverseJoinColumns = @JoinColumn(name = "Vitamin_id"))
  13.     private List<Vitamin> vitamins;
  14.  
  15.     // getters, setters...
  16.  
  17. }

Vitamin.java:

JAVA:
  1. @SequenceGenerator(name = "sq_vitamin", sequenceName = "sq_vitamin")
  2. public class Vitamin {
  3.  
  4.     @Id
  5.     @GeneratedValue(strategy = GenerationType.AUTO, generator = "sq_vitamin")
  6.     private Integer id;
  7.     private String name;
  8.  
  9.     // getters, setters...
  10.  
  11. }

Em uma busca simples, poderíamos recuperar uma lista de todas os frutos que contém a vitamina C:

JAVA:
  1. Criteria criteria = session.createCriteria(Fruit.class, "fruit");
  2. criteria.createAlias("fruit.vitamins", "vitamin");
  3. criteria.add(Restrictions.eq("vitamin.name", "Vitamin C"), Criteria.LEFT_JOIN);
  4.  
  5. List<Fruit> fruits = criteria.list();
  6.  
  7. for (Fruit f: fruits) {
  8.     System.out.println("Fruit: " + f.getName());
  9.     System.out.println("--Vitamin: " + f.getName());
  10. }

Como pedimos, esta consulta retorna uma lista de frutos. Porém, cada fruto retornado conterá apenas uma vitamina: a vitamina C!


Fruit: Pinnapple
--Vitamin: Vitamin C
Fruit: Orange
--Vitamin: Vitamin C
Fruit: Banana
--Vitamin: Vitamin C

Foi exatamente o que pedimos quando adicionamos Restrictions.eq("vitamin.name", "Vitamin C") para a criteria.

Até aqui tudo bem, se apenas fossem necessárias os frutos. E se precisássemos agora fazer a mesma consulta (por "vitamica C"), mas gostariámos de ver todas as vitaminas que cada fruto contém? Usamos subquery!

JAVA:
  1. // todos frutos que contém a vitamina C
  2. DetachedCriteria dc = DetachedCriteria.forClass(Fruit.class, "fruit");
  3. dc.createAlias("fruit.vitamins", "vitamin", Criteria.INNER_JOIN);
  4. dc.add(Restrictions.eq("vitamin.name", "Vitamin C"));
  5. dc.setProjection(Property.forName("fruit.id"));
  6.  
  7. // mas quero ver as outras vitaminas que o fruto tem
  8. Criteria criteria = session.createCriteria(Fruit.class, "fruit");
  9. criteria.createAlias("fruit.vitamins", "vitamin", Criteria.LEFT_JOIN);
  10. criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
  11. criteria.add(Property.forName("fruit.id").in(dc));
  12.  
  13. List<Fruit> list = criteria.list();
  14.  
  15. for (Fruit fruit : list) {
  16.     System.out.println("Fruit: " + fruit.getName());
  17.     System.out.println("--Vitamins: " + fruit.getVitamins());
  18. }

O método in() da classe Property recebe uma DetachedCriteria. Uma DetachedCriteria nos permite criar uma consulta fora de uma sessão, para ser executada posteriormente em uma sessão arbitrária.

A saída no console, com o logger da query:

JAVA:
  1. Hibernate:
  2. select
  3.     this_.id as id1_1_,
  4.     this_.name as name1_1_,
  5.     vitamins3_.Fruit_id as Fruit1_3_,
  6.     vitamin1_.id as Vitamin2_3_,
  7.     vitamin1_.id as id0_0_,
  8.     vitamin1_.name as name0_0_
  9. from
  10.     Fruit this_
  11. left outer join
  12.     Fuits_Vitamins vitamins3_
  13.         on this_.id=vitamins3_.Fruit_id
  14. left outer join
  15.     Vitamin vitamin1_
  16.         on vitamins3_.Vitamin_id=vitamin1_.id
  17. where
  18.     this_.id in (
  19.         select
  20.             fruit_.id as y0_
  21.         from
  22.             Fruit fruit_
  23.         inner join
  24.             Fuits_Vitamins vitamins3_
  25.                 on fruit_.id=vitamins3_.Fruit_id
  26.         inner join
  27.             Vitamin vitamin1_
  28.                 on vitamins3_.Vitamin_id=vitamin1_.id
  29.         where
  30.             vitamin1_.name=?
  31.     )


Fruit: Pineapple
--Vitamins: [Vitamin C, Vitamin B6]
Fruit: Orange
--Vitamins: [Vitamin C, Vitamin B6]
Fruit: Banana
--Vitamins: [Vitamin C, Vitamin A]

Tem outro exemplo aqui, usando Detached queries and subqueries.

Por Rodrigo Facholi em 9/October/2008 | Comentar | Trackback


No Translations

Adicionar comentário

(requerido)
(requerido, não será publicado)