I have an entity which contains a list of elements and now I want to search over attributes of these elements. This constraint should be "and" connected. Please see these simple example:
#Entity
public class Parent {
#Column
#Enumerated(EnumType.STRING)
private City city;
#OneToMany(...)
private List<Children> childrens;
}
#Entity
public class Children {
#Column
#Enumerated(EnumType.STRING)
private School school;
#Column
private Integer yearInSchool;
}
Now I want to find Parents in a certain city, lets say "BigCity" with children in School "AwesomeSchool" which are in class/ year 6. I want to get the search result only via CriteriaBuilder.
So far I got:
final CriteriaBuilder c = getCriteriaBuilder();
final CriteriaQuery<Parent> query = c.createQuery(Parent.class);
final Root<Parent> r = query.from(Parent.class);
query.select(r)
.where(c.and(c.equal(r.get("city"), City.BigCity)),
c.equal(r.get("childrens").get("school"), School.AwesomeSchool),
c.equal(r.get("childrens").get("yearInSchool"), 6));
Unfortunately there are two problems here:
- it looks like I can't call get("school") on the list attribute
- this will return all parents with children which are either in "AwesomeSchool" or are 6 years in the school.
Can you help me please? I thought about using a join, but there the same question is: how can I define the where part of the join so that it considers that both attributes (school and yearInSchool) have to be fulfilled at the same time.
I found similar posts about querying for objects whose children fulfill one condition - but here the children has to fulfill two conditions at the same time.
Update 1
If I use a join to assert e.g. the "school" of one child, I get so far concerning the predicate:
Predicate predicate = r.join("childrens").get("school").in(School.AwesomeSchool)
How can I reuse this joined object to assert is also for the second filter condition?
You need to JOIN and then use the JOIN object you got when forming the join when forming the WHERE clauses.
Join childrenJoin = r.join("childrens");
query.where(c.and(c.equal(r.get("city"), City.BigCity)),
c.equal(childrenJoin.get("school"), School.AwesomeSchool),
c.equal(childrenJoin.get("yearInSchool"), 6));
Perhaps you mean your JPQL to be :
SELECT p FROM Parent p JOIN p.childrens c
WHERE p.city = :theCity AND c.school = :theSchool AND c.yearInSchool = 6
Related
I have something like this
public class Car {
private List<CarCategory> categories;
...
}
so every Car can have multiple enum categories, like "FAMILY", "SPORTCAR", "PREMIUM", "AFFORDABLE" etc
I need to be able to get all cars that have all the categories in a specified/given list, for example "all cars that have FAMILY and AFFORDABLE".
All the examples that i have found using "builder.in" assumed that a Car can only have 1 category, but this is not what i want
Any help would be very appreciated, thank you
It may not be the most optimal from the query point of view, but something like this would be functionally correct:
List<String> categories = new ArrayList<>();
cagetories = getCategoriesToFilter();
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> root = cq.from(Car.class);
for (String category : categories){
//We create an inner join to filter those cars that do not have a category
Join<Car,CarCategory> join = root.join("categories",JoinType.INNER);
join.on(cb.equals(join.get("description"),category));
}
cq.select(root);
//Due to the inner joins, at this point all the cars that have all the categories of
//"categories" will appear.
List<Car> result = entityManager.createQuery(cq).getResultList();
I have an Entity with a ManyToOne Relationship to the Primary Key of another entity. When I create a query that references this Foreign Key eclipseLink always creates a join instead of simply accessing the Foreign Key.
I have created a highly simplified example to show my issue:
#Entity
public class House {
#Id
#Column(name = "H_ID")
private long id;
#Column(name = "NAME")
private String name;
#ManyToOne
#JoinColumn(name = "G_ID")
private Garage garage;
}
#Entity
public class Garage{
#Id
#Column(name = "G_ID")
private long id;
#Column(name = "SPACE")
private Integer space;
}
I created a query that should return all houses that either have no garage or have a garage with G_ID = 0 using the CriteriaBuilder.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garageId)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
The generated query is:
SELECT h.NAME, h.G_ID FROM HOUSE h, GARAGE g WHERE (((h.G_ID= 0) OR (g.G_ID IS NULL)) AND (g.G_ID = h.G_ID));
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
The join is created in the first place.
The correct query should look like this in my understanding:
SELECT h.NAME, h.G_ID FROM HOUSE h WHERE (((h.G_ID= 0) OR (h.G_ID IS NULL));
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
SELECT h.NAME, h.G_ID FROM HOUSE h LEFT OUTER JOIN GARAGE g ON (h.G_ID = g.G_ID ) WHERE (h.G_ID = 0) OR (g.G_ID IS NULL);
(Note both these queries would work correctly in my more complicated setup. I also get the same error when only wanting to retrieve all houses that have no garage.)
How can I achieve this (while still using the CriteriaBuilder and ideally not having to change the DB Model)?
(Please let me know any additional information that might be required, I'm very new to this topic and came across this issue while migrating an existing application.)
-- edit --
I have found a solution to my problem that will result in slightly different behaviour (but in my application that part of the code I had to migrate didn't make much sense in the first place). Instead of using
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
I use
Path<Garage> garage = houseRoot.get(House_.garage);
And then as expected table Garage isn't joined anymore. (I assume the code previously must have been some kind of hack to get the desired behaviour from openJPA)
I don't understand why
The or condition first references table HOUSE and then GARAGE (instead of HOUSE)
I believe this is implementation specific; in any case, it shouldn't have any bearing on the results.
The join is created in the first place.
By saying Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id) you're basically telling EclipseLink: 'join Garage to House, we're gonna need it'. That you then access Garage_.id (and not, for example, Garage_.space) is inconsequential.
If you don't want the join, simply map the G_ID column one more time as a simple property: #Column(name = "G_ID", insertable = false, updatable = false) private Long garageId. Then refer to House_.garageId in your query.
Or if a join is made it should take into account that the ManyToOne relationship is nullable and therefore do a LEFT OUTER JOIN.
Path.get(...) always defaults to an INNER JOIN. If you want a different join type, use Root.join(..., JoinType.LEFT), i. e. houseRoot.join(House_.garage, JoinType.LEFT).get(Garage_.id).
One solution that results in the same behaviour is:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Garage> garage = houseRoot.get(House_.garage);
Path<Long> garageId = garage.get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garage)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();
This results in the following SQL:
SELECT H_ID, NAME, G_ID FROM HOUSE WHERE ((G_ID = 0) OR (G_ID IS NULL));
I am using spring data jpa:
What is the best way to filter child object on the parent.
on my below example
I want Parent objects which has active childs, also wanted only active childs as list with parent
#Query(select distinct p from Parent p inner join p.child c where c.active=:active)
Page<Parent> getAllPArents(#Param("active") int active);
#Entity
Parent{
#OneToMany(fetch=FetchType.LAZY)
List<Child> child;
}
#Entity
Child{
#ManyToOne
Parent parent;
}
I had exactly the same problem and it took me a while to find out how this works. The child list will be filtered when you add FETCH after your JOIN like this:
SELECT p FROM Parent p JOIN FETCH p.child c WHERE c.active=:active
After spending lot of time, I could not find any solution to filter the child while querying the parent.
I decided to change the database structure so that I will be able to query child that I wanted. Added another filed/property that can put child into different categories. When querying child using unique criteria that will give me only the child I needed, also gave me the parent which I needed too.
I found the way to do this:
You can use the #Where annotation to filter the childs of a one to many or a many to many relationship.
public class Parent {
#OneToMany(mappedBy="Parent")
#Where(clause = "active = 1") //In case of a boolean but it can be a LIKE, a comparator...
private Set<Child> childs; //This gets filled with pets with a name starting with N
//getters & setters
}
I'm having a Product object with a list of related products (which are also product objects). The field of related products is annotated like this:
public class Product {
#JoinTable(name = "RELATED_PRODUCT", joinColumns = {
#JoinColumn(name = "PRODUCT_ID", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "RELATED_PRODUCT_ID", referencedColumnName = "id", nullable = false)})
#ManyToMany(fetch = FetchType.LAZY)
List<Product> relatedProducts;
}
As you can see the list is fetched lazy, which is what I want in most cases. In some cases however, I want the list of related products to be filled immediatly. I created a query for this with a LEFT JOIN FETCH. However, I want only the related products to be added that have a certain rating, let's say a rating of > 3.
I tried the following:
SELECT DISTINCT p FROM Product p LEFT JOIN FETCH p.comparableProducts cp WHERE p.id = :id AND cp.rating > 3 AND CURRENT_DATE BETWEEN p.commenceDate AND p.removeDate
But this doesn't work. It always returns back ALL related products in the database, not just the ones that have a rating above 3. How is this fixable?
The easiest way to solve this problem is to load related products separately instead of trying to fit them into relatedProducts field.
It also makes perfect sense from object oriented point of view. I suppose you have something like "Product page" that contains the selected product and "recommended products". If so, such a page is a separate concept that deserves its own class:
public class ProductPage {
private Product product;
private List<Product> recommendedProducts;
...
}
Then you can fill such a class either by a single query:
SELECT DISTINCT p, cp FROM Product p LEFT JOIN p.comparableProducts cp WHERE p.id = :id AND cp.rating > 3 AND CURRENT_DATE BETWEEN p.commenceDate AND p.removeDate
or by two separate queries.
Unfortunately, this approach doesn't allow you to receive an instance of ProductPage directly from JPA, you need to write conversion code manually.
I have two entities: parent Customer and child Order.
Each Customer has 1,000,000 Orders for example, so it is not needed in any given time to load a Customer with all Orders but I want to have this ability to make join query on these two entities in JPA.
So because of this, I must create #OneToMany relationship for making join queries.
My question is: how to get query without making joinColumn because even in Lazy mode it is possible to load 1,000,000 objects!
I just want to get query on these object with where restrictions like native join.
If you don't want the #OneToMany relationship implicitly set in your Customer class than you don't have to. You can execute JPQL queries (in very precise manner) without the marked relationship.
Assume you have:
#Entity
public class Customer {
// all Customer-related fields WITHOUT #OneToMany relationship with Order
}
#Entity
public class Order {
#ManyToOne
private Customer owner;
}
Then if you want to get all Orders for particular Customer you can execute a simple JPQL query like that:
// Customer customer = ...
// EntityManager em = ...
String jpql = "SELECT o FROM Order o WHERE o.owner = :customer";
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("customer", customer);
List<Order> orders = query.getResultList();
In this way you can execute the code only when you're really sure you want to fetch Customer's orders.
I hope I've understood your problem correctly.
EclipseLink has support for QueryKeys, that allow you to define fields or relationships for querying that are not mapped. Currently there in no annotation support for query keys, but you can define them using the API and a DescriptorCustomizer.
Also you do not need the OneToMany to query on it, just use the inverse ManyToOne to query,
i.e.
Select distinct c from Customer c, Order o where o.customer = c and o.item = :item
Or,
Select distinct o.customer from Order o join o.customer c where o.customer = c and o.item = :item