I have a project built on Spring MVC + JPA + Hibernate. I am using entity graphs (JPA 2.1) for defining which data to fetch from the database, like in the example below.
EntityGraph<Company> entityGraph = entityManager.createEntityGraph(Company.class);
entityGraph.addAttributeNodes("reviews");
Map<String, Object> hints = new HashMap<String, Object>();
hints.put("javax.persistence.loadgraph", entityGraph);
Company company = entityManager.find(Company.class, companyId, hints);
My Review
entity has an association with a Company
entity (ManyToOne
).
Here I simply fetch a Company
object with a populated reviews
collection. This works well in scenarios like the above. But what if I want to fetch all or some of the reviews of a given company? That is, the Review
objects that are associated with a company with a given ID. I want a List<Review>
instead of a Company
object with a List<Review>
. This is just an example - basically I am looking for more flexibility than simply looking up an object based on a primary key. I can do it with HQL with no problem, but then I would have to write several similar queries depending on which data I need in a specific context.
The find
method on javax.persistence.EntityManager
simply makes it possible to query for an object based on a primary key. But is it somehow possible to use entity graphs in more complicated scenarios, e.g. with Criteria objects or HQL queries? For example, looking up objects with other conditions than by primary key - perhaps even conditions on associations.
I hope I made myself clear. Thanks in advance!
What you are looking for is probably not EntityGraph
s, but JPA Query
(either JPQL in form of a NamedQuery
or a CriteriaQuery
). This is all part of the JPA specification.
So basically you can:
@NamedQueries
to specify JPQL queries. Their advantage is that their syntax is checked on deployment (deployment will fail if e.g. the NamedQueries access missing properties) and are reusable and disadvantage is: they are statically defined (but of course accept parameters).Now about EntityGraph
s: they are just a help so fetch additional fields from a query (no matter if you use EntityManager.find()
with the additional properties map parameter or Query.setHints()
). You could also use Subgraphs for more complex situations. Check this and this example.
As you note:
I can do it with HQL with no problem, but then I would have to write several similar queries depending on which data I need in a specific context.
I assume then that the ultimate solution would look something like the below where one query method can be defined but to which we can pass both dynamic criteria (be these either direct or nested properties of an Entity) and a dynamic fetch plan defining those EntityGraphs to be activated:
public interface CompanyRepository{
List<Review> findAll(Criteria criteria, FetchPlan fetchPlan);
}
The Spring Data project can actually get us part of the way there but not, at the current time, all of the way (although there is some discussion around regarding the missing part: see further below).
What it does do at the moment is the first part:
public interface CompanyRepository{
List<Review> findAll(Criteria criteria);
}
either by means of the Specification pattern (http://docs.spring.io/spring-data/jpa/docs/1.8.0.M1/reference/html/#specifications) or, more simply, by using QueryDSL's fluent API as an alternative to the JPA Criteria API.
Using the QueryDSL approach then we can create a Repository definition like this:
public interface ReviewRepository extends CrudRepository<Review, Long>, QueryDslPredicateExecutor<Review>{
}
Now, and without creating an implementation (created by the framework) or writing any more code we can call it as below, with any combination of attributes:
Review review = respository.findOne();
Iterable<Review> reviews = respository.findAll(QReview.review.created.eq(someDate));
Iterable<Review> reviews = respository.findAll(QReview.review.created.eq(someDate).and(QReview.review.creator.forename.eq("Jim"));
Iterable<Review> reviews = respository.findAll(QReview.review.created.eq(someDate).and(QReview.review.creator.forename.eq("Jim").and(QReview.review.company.name.eq("Amazon"));
etc....
where QReview is a Query Type auto generated by the QueryDSL library and giving you strongly typed queries and the findOne(Predicate predicate) and findAll(Precicate predicate) methods are inherited from:
http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/querydsl/QueryDslPredicateExecutor.html
This then gives you a lot for very little (zero) code other than creating some interface definitions but would still require an additional (sub-optimal in terms of DB interactions) mechanism for handling the lazy loading i.e. one of the mechanisms highlighted in your other recent question and which led on to this one.
I do not see however why Spring Data could not be updated to handle the dynamic specification of EntityGraphs and there has indeed been some discussion around this so it may be in the pipeline:
http://forum.spring.io/forum/spring-projects/data/108202-custom-fetch-groups-with-spring-data-jpa-possible
so might be worth raising a JIRA to see if this something they are planning or asking
https://stackoverflow.com/users/18122/oliver-gierke
directly.
Some support has been added to Spring Data for the new JPA 2.1 EntityGraph functionalityb however I do not see that it can be made to work with a generic query method like we want here:
http://docs.spring.io/spring-data/jpa/docs/1.8.0.M1/reference/html/#jpa.entity-graph
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With