09 out 2008

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:

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@SequenceGenerator(name = "sq_fruit", sequenceName = "sq_fruit")
public class Fruit {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "sq_fruit")
    private Integer id;
    private String name;
    @ManyToMany(cascade = CascadeType.ALL)
    @ForeignKey(name = "fk_Fuit", inverseName = "fk_Vitamin")
    @JoinTable(    name = "Fuits_Vitamins",
            joinColumns = @JoinColumn(name = "Fruit_id"),
            inverseJoinColumns = @JoinColumn(name = "Vitamin_id"))
    private List<Vitamin> vitamins;

    // getters, setters...

}

Vitamin.java:

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
@SequenceGenerator(name = "sq_vitamin", sequenceName = "sq_vitamin")
public class Vitamin {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "sq_vitamin")
    private Integer id;
    private String name;

    // getters, setters...

}

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

1
2
3
4
5
6
7
8
9
10
Criteria criteria = session.createCriteria(Fruit.class, "fruit");
criteria.createAlias("fruit.vitamins", "vitamin");
criteria.add(Restrictions.eq("vitamin.name", "Vitamin C"), Criteria.LEFT_JOIN);

List<fruit> fruits = criteria.list();

for (Fruit f: fruits) {
    System.out.println("Fruit: " + f.getName());
    System.out.println("--Vitamin: " + f.getName());
}

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

1
2
3
4
5
6
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!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// todos frutos que contém a vitamina C
DetachedCriteria dc = DetachedCriteria.forClass(Fruit.class, "fruit");
dc.createAlias("fruit.vitamins", "vitamin", Criteria.INNER_JOIN);
dc.add(Restrictions.eq("vitamin.name", "Vitamin C"));
dc.setProjection(Property.forName("fruit.id"));

// mas quero ver as outras vitaminas que o fruto tem
Criteria criteria = session.createCriteria(Fruit.class, "fruit");
criteria.createAlias("fruit.vitamins", "vitamin", Criteria.LEFT_JOIN);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.add(Property.forName("fruit.id").in(dc));

List<fruit> list = criteria.list();

for (Fruit fruit : list) {
    System.out.println("Fruit: " + fruit.getName());
    System.out.println("--Vitamins: " + fruit.getVitamins());
}

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:

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

Rodrigo Facholi
About Rodrigo Facholi

Desenvolvedor há 9 anos, e há 7 trabalhando na redspark.

Comments

  • Dyego
    dezembro 19, 2011 Responder

    Muito obrigado, vc salvou meu dia hoje!!!!!

Leave a Comment