Eu sempre achei a linguagem SQL muito interessante, por ter quê de linguagem natural. Aliás, a idéia inicial era realmente criar uma linguagem de consultas, bem próxima à maneira como nós, humanos, nos comunicamos. O tempo passou, a linguagem cresceu, e as consultas SQL se tornaram verdadeiros monstros. É difícil encontrar uma query mais complexa que contenha fluidez similar a uma sentença em inglês. Além disso, eu sempre achei chato misturar SQL no meio da minha aplicação (mas isso é uma birra minha que não tem nada a ver com a história).
Há cerca de 3 anos fui apresentado a uma API de consultas do Hibernate (Hibernate Criteria API). A idéia é que programaticamente poderíamos construir consultas SQL, ao invés de usar a linguagem de consultas do Hibernate (HQL). Achei a idéia muito boa e fiz uso desta API por alguns anos.
No mundo JPA (Java Persistence API), somente com a JPA 2, passamos a ter acesso a uma API de criteria. Existem inúmeras vantagens e desvantagens em usar uma API de criteria, mas as que eu gosto de destacar são:
- Verificação de erros – Muitos erros podem ser detectados em tempo de compilação;
- Segurança – como as consultas são construídas pelo motor da API, você fica praticamente imune a SQL injections;
- Queries dinâmicas podem ser construídas mais facilmente, ao invés montar strings complexas.
- Tipagem forte – a JPA criteria API leva vantagem em relação à Hibernate Criteria API em relação a verficação de tipos.
- Complexidade – uma vez que a maioria dos desenvolvedores está acostumada com o SQL/HQL/JPQL, migrar para uma API de criteria não é simples.
Executando uma query JPQL:
Query query = entityManager.createQuery("SELECT p FROM Product p");
List results = query.getResultList();
A mesma query JPQL, mas de forma tipada, seria:
TypedQuery<Product> typedQuery = entityManager.createQuery("SELECT p FROM Product p", Product.class);
List<Product> results = typedQuery.getResultList();
Executando esta consulta usando a JPA Criteria API:
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Product> query = builder.createQuery(Product.class); Root<Product> from = query.from(Product.class); CriteriaQuery<Product> select = query.select(from); TypedQuery<Product> typedQuery = entityManager.createQuery(select); List<Product> results = typedQuery.getResultList();
Perceba que os dois últimos comandos são bem parecidos com a maneira como a JPQL funciona. A primeira vista, esta estratégia parece ser bem mais complexa e trabalhosa, mas se encurtarmos um pouco o código, temos uma estrutura bem parecida com uma query SQL:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = builder.createQuery(Product.class);
TypedQuery<Product> typedQuery = entityManager.createQuery(
query.select(
query.from(Product.class)
)
);
List<Product> results = typedQuery.getResultList();
Veja um exemplo de como fazermos uma consulta com a cláusula where.
Usando a JPQL:
TypedQuery<Product> typedQuery = entityManager.createQuery(
"SELECT p "+
"FROM Product p "+
"WHERE p.price > :price",
Product.class);
typedQuery.setParameter("price", price);
List<Product> results = typedQuery.getResultList();
Usando a JPA Criteria API:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = builder.createQuery(Product.class);
Root<Product> from = query.from(Product.class);
TypedQuery<Product> typedQuery = entityManager.createQuery(
query.select(from )
.where(
builder.gt(from.get("price"), price)
)
);
List<Product> results = typedQuery.getResultList();
Sim, tudo bem, eu admito, está tudo muito mais complicado do que as queries JPQL. Mas eu ainda acho que queries com muitos componentes dinâmicos são mais fáceis de fazer, quando usamos a JPA Criteria API.
Vamos dar uma olhada numa query que envolva múltiplas entidades (joins).
Usando a JPQL:
TypedQuery<Product> typedQuery = entityManager.createQuery(
"SELECT p "+
"FROM Product p "+
"WHERE p.supplier.name=:supplier",
Product.class);
typedQuery.setParameter("supplier", supplierName);
List<Product> results = typedQuery.getResultList();
Usando a JPA Criteria API:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = builder.createQuery(Product.class);
Root<Product> from = query.from(Product.class);
TypedQuery<Product> typedQuery = entityManager.createQuery(
query.select(from )
.where(
builder.equal(from.join("supplier").get("name"), supplierName)
)
);
List<Product> results = typedQuery.getResultList();
Para fazer uma ordenação, basta usar o método CriteriaQuery.orderBy():
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = builder.createQuery(Product.class);
Root<Product> from = query.from(Product.class);
TypedQuery<Product> typedQuery = entityManager.createQuery(
query.select(from )
.where(
builder.gt(from.get("price"), price)
)
.orderBy(builder.asc(from.get("name")))
);
List<Product> results = typedQuery.getResultList();
Vejamos uma consulta resultado de uma tela de busca onde o usuário pode filtra por diversos critérios: maior preço, menor preço, nome e categoria. A estratégia será adicionar mais ou menos cláusulas ao predicado usado pelo where.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = builder.createQuery(Product.class);
Root<Product> from = query.from(Product.class);
Predicate predicate = builder.and();
// product.price > minPrice
if (minPrice != null && minPrice > 0){
predicate = builder.and(predicate, builder.ge(from.get("price"), minPrice));
}
// product.price < maxPrice
if (maxPrice != null && maxPrice > 0){
predicate = builder.and(predicate,
builder.le(from.get("price"), maxPrice));
}
// product.name like %productName%
if (productName != null && productName.length > 2){
predicate = builder.and(predicate,
builder.like(from.<String>get("name"), "%"+productName+"%"));
}
// product.category.name like %categoryName%
if (categoryName != null && categoryName.length() > 2){
predicate = builder.and(predicate,
builder.like(
from.join("category").<String>get("name"),
"%"+categoryName+"%"));
}
TypedQuery<Product> typedQuery = entityManager.createQuery(
query.select(from )
.where( predicate )
.orderBy(builder.asc(from.get("name")))
);
List<Product> results = typedQuery.getResultList();
Recentemente precisei fazer uma consulta a partir de um relacioamento unidirecional que me deu um certo trabalho. Fazendo algumas simplificações, eu queria obter todos professores (Teacher) de turmas (ClassSection) de uma determinada escola (School). Turma aponta para professor, mas professor não aponta para turma.
Teacher <- ClassSection -> School
Aconsulta ficou mais ou menos assim:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Teacher> query = builder.createQuery(Teacher.class);
Root<Teacher> fromTeacher = query.from(Teacher.class);
Root<ClassSection> fromClassSection = query.from(ClassSection.class);
Join<ClassSection, Teacher> teacherJoin = fromClassSection.join("teacher");
Join<ClassSection, School> schoolJoin = fromClassSection.join("school");
TypedQuery<Teacher> typedQuery = getEntityManager().createQuery(query
.select(fromTeacher)
.where(builder.and(
builder.equal(fromTeacher, teacherJoin),
builder.equal(schoolJoin.get("id"), schoolId)
))
.orderBy(builder.asc(fromTeacher.get("firstName")))
.distinct(true)
);
List<Teacher> teachers = typedQuery.getResultList();
Perceba que faço a cahamada query.from() duas vezes, uma para Teacher e outra para ClassSection, entreatanto, o método createQuery() sempre terá o tipo da classe que será retornada como resultado da consulta.
Para saber mais:
http://www.objectdb.com/java/jpa/query/criteria
http://www.altuure.com/2010/09/23/jpa-criteria-api-by-samples-part-i/
http://www.altuure.com/2010/09/23/jpa-criteria-api-by-samples-%E2%80%93-part-ii/
Comentários
Powered by Facebook Comments