Técnico

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:


@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:


@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:


Criteria criteria = session.createCriteria(Fruit.class, "fruit");
criteria.createAlias("fruit.vitamins", "vitamin");
criteria.add(Restrictions.eq("vitamin.name", "Vitamin C"), Criteria.LEFT_JOIN);

List 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!


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!


// 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 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:


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=?
)


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.

Autor(a)

Rodrigo Facholi

Comentário (1)

  1. Dyego
    19 de dezembro de 2011

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

Deixe um comentário

O seu endereço de e-mail não será publicado.