Hibernate JPA Meta Model -- reference nested properties? - java

Suppose an entity model where an Employee has a Supervisor who has an id. Using hibernate-jpamodelgen to generate the meta model for the entities, how can I query a nested field?
For instance, "get all employees whose supervisor has id 4", using JpaSpecificationExecutor:
Page<Employee> getEmployeesBySupervisorId(int id) {
return findAll((root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get(Employee_.supervisor.id), id);
});
}
Note that Employee_ is the model meta class for Employee (and was generated by Hibernate).
This code will produce an error because the id symbol cannot be found on type SingularAttribute<Employee, Supervisor>. I get that, but it seems like these should somehow be chainable. I can't find great examples of how to do this cleanly.

In order to navigate to related entities, you must use From#join() join method, which works well with MetaModel:
CriteriaQuery<Employee> cq = criteriaBuilder.createQuery(Employee.class);
Root<Employee> from = cq.from(Employee.class);
Predicate p = criteriaBuilder.equal(from.join(Employee_.supervisor).get(Supervisor_.id), id);
See also
Oracle's Java EE Tutorial - Using the Criteria API and Metamodel API to Create Basic Typesafe Queries

Yes, I also stumbled upon this problem that the Metamodel classes are not offering deeper visibility to relationships > 1.
While accessing A.b is possible, A.b.c is not.
But there is another possibility besides Joins:
Just concatenate by using several getter(). For this you will need a root element (= CriteriaQuery & CriteriaBuilder).
return criteriaBuilder.equal(root.get(Employee_.supervisor).get(Supervisor_.id), id);
While this still ensures type safety, the whole path should be correct as it is not validated until runtime.
Also for sorting a resultset using the Metamodel there is a similar solution. Say you want to sort by the Supervisor's id:
Use JpaSort and JpaSort.Path
JpaSort.of(JpaSort.Direction.ASC, JpaSort.path(Employee_.supervisor).dot(Supervisor_.id));

Related

how to transform JPA specification from child entity to parent entity

I have a rather big system with a specification that is built by several methods on a child entity. So I have a User with a OneToMany on pets, as in this question. My relation is bidirectional, so Pet also has a ManyToOne relationship on User, and I'm struggling in transforming the specification on child entity to apply on parent entity instead.
Most questions I looked up showed how to apply a specification on a different entity, OR to get a different entity once the specification was executed. This is NOT what i'm looking for.
I'm trying to write a method like this :
public static Specification<User> extendsPetSpecToUser(
Specification<Pet> petSpec) {
// ???
}
but I don't know how to write it (I tried using Join, but didn't manage to "say" to JPA to combine the join and the specification to query with a different root but similar constraints)
Given the specification is big AND also used in other parts to query directly for Pet, rewriting it from a different root isn't really an option.
Thank you for your time, and sorry if this is a duplicate, I really didn't see another question matching my needs.
First, it feels like this problem hides a weird construction of the queries you use.
Why would you build a Specification<Pet> if you need Specification<User> at the end ?
There might be a code architecture to think about.
Anyway, to achieve the mentioned goal, have you tried using subqueries ?
public class TransformToUser implements Specification<User> {
private final Specification<Pet> specification;
public TransformToUser (Specification<Pet> specification) {
this.specification = specification;
}
#Override
public Predicate toPredicate(
Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Subquery<Pet> subqueryPet = criteriaQuery.subquery(Pet.class);
Root<Pet> fromPet = subqueryDeclaration.from(Pet.class);
subqueryPet.select(fromPet);
subqueryPet.where(
specification.toPredicate(fromPet, criteriaQuery, criteriaBuilder));
return root.get(User_.pets).in(subqueryPet);
}
}

JpaSpecificationExecutor, how to change the field in SELECT

dears.
Currently, I am working with JpaSpecificationExecutor API, cause I need to create dynamic queries based on a lot of optional search criteria, for this I am starting with a root entity, which is included in the generic type in the JpaSpecificationExecutor interface.
The code to make the build in the Specification (which is included in the findAll method that belongs to repository interface) is the following:
public Specification<T> build(){
return (root, query, criteriaBuilder) -> {
Predicate filters = null;
//createPredicate is a method to create predicates (it does not matter in this code)
filters = createPredicate(root, criteriaBuilder, searchCriterias);
return filters;
};
}
Now each is working fine, but there is a little detail, in the query I am using joins to make relation with other entities, but when I get the response, like in this code:
List<EXAMPLE> examples = repository.findAll(filters.build());
I am not able to see the filter data in other entities, just I can see the field in the root entity (It is EXAMPLE entity) because these ones are appearing in the query formed. so, I would like to include in the SELECT the other entities, in order to see the filter data by means of the JOINs, but until now I have not been able to do it.
I have seen in any inputs, that I can use these codes in my build code, I have tried, but the new files are not appearing in the generated query:
query.select, or query.multiselect
Ref:
Ref. 1
Also, I have found that this is not working with JpaSpecificationExecutor, because the list of attributes in the SELECT is assigned by the entity Ref. 2
My question is, does someone know if exists another way to change the field in the SELECT with JpaSpecificationExecutor.
Regards.

utilizing JPA Named Queries

When I create an Entity class from a database in NetBeans, it gives me the option to create Named Queries from persistent fields. Accordingly, I see these named queries listed at the top of my Entity class.
What exactly are these queries, and how can I utilize/"call" them?
I'm aware this question is more general than is preferred on SO, so I'm happy to accept a link to a tutorial that answers these questions, but I've been unable to find one myself.
See
JPA Named Queries
If you have:
#NamedQuery(name="Country.findAll", query="SELECT c FROM Country c")
public class Country {
...
}
Use with:
TypedQuery<Country> query = em.createNamedQuery("Country.findAll",Country.class);
List<Country> results = query.getResultList();
See also:
Annotation Type NamedQuery
Tutorial: Build a Web Application (JSF) Using JPA

How to bulk delete from element collection in jpa

I'm using jpa 2.0 and I have the following entity:
#Entity
public class Folder{
#ElementCollection
#CollectionTable(name="folder_files")
private Set<String> files;
// .....
}
Given a file name, I would like to delete all entries where files == theGivenFileName. In sql it would be something like this:
Delete from folder_files where files = XXX
Is there a way to perform this query using criteria-api?
If not, is there a way to perform this query using jpql?
UPDATE:
I think my question was not clear enough:
Since jpql uses entities (and not tables) I cannot just perform the sql written above plus since I'm using #ElementCollection I don't know how to address this variablr or even deal with it. I would like to delete all entries in that collection (in my case, the files set) which holds a given value, from all entities. Is that possible using jpql or (even better) criteria-api?
The Delete FROM clause requires an Entity, so there is no way to delete from an element collection from what I understand.
You can use a native SQL query, or you can map the element collection as a OneToMany to an Entity instead.
You can use the like query just the syntax is slightly changed.
query = em.createQuery("SELECT i FROM Item i WHERE UPPER(i.name) LIKE :keyword ");
query.setParameter("keyword", "%" + keyword.toUpperCase() + "%");
You can read more on following link,
https://forums.oracle.com/forums/thread.jspa?threadID=423742
Updated:
#Noam you can do it: Like in Criteria API
List cats = sess.createCriteria(Cat.class)
.add( Restrictions.like("name", "Fritz%") )
.add( Restrictions.between("weight", minWeight, maxWeight) )
.list();
Kindly read more on it at following link:
http://ctpconsulting.github.com/query/1.0.0.Alpha3/criteria.html
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html
This cannot be done. Via JPQL it does not work because DELETE statement can only be applied to entities. Excerpt from JPA 2.0 specification:
Bulk update and delete operations apply to entities of a single entity
class (together with its subclasses,if any).
...
delete_statement ::= delete_clause [where_clause]
delete_clause ::= DELETE FROM entity_name [[AS] identification_variable]
Also it doesn't work via Criteria API. CriteriaQuery supports only selecting - not updates or removals.
You have to go for native SQL.

QueryDSL code generation for ManyToMany

I'm porting some complex JPQL queries in a large Hibernate/JPA2 application to use QueryDSL 2.3.0, and I'm stuck on one.
My Client entity contains
#ManyToMany
private List<Group> groups;
My existing query fragment is
EXISTS(SELECT g FROM Group g WHERE g MEMBER OF slr.groups AND
UPPER(g.description) LIKE :group)
The QueryDSL code generation has produced the following in my QClient class:
public final SimplePath<java.util.List<Group>> groups =
createSimple("groups", java.util.List.class);
The code generation using SimplePath doesn't let me use the in or contains methods to query membership. I think I need a CollectionPath instead. Is there a way to annotate the Client class so that QueryDSL uses the correct type for querying a collection?
I have an answer. This looks like a bug introduced in QueryDSL 2.2.5, which only happens when working in Eclipse.
The correct solution is to not use Eclipse to generate the source (don't enable annotation processing). Instead, I'm using m2eclipse and generating the source in Maven.
For reference, my first workaround was to extend the generated QClient class with my own QQClient class, which adds one member:
public final ListPath<Group, QGroup> fixedgroups =
createList("groups", Group.class, QGroup.class);
At that point the equivalent to my original query is:
QGroup g = QGroup.group;
JPQLSubQuery subquery = new JPQLSubQuery().from(g);
subquery = subquery.where(slr.fixedgroups.contains(g),
g.description.upper().like("%" + group.toUpperCase() + "%"));
query = query.where(subquery.exists());
(query is the larger query this is part of. slr is an instance of QQClient brought into the outer query by a left join.)

Categories