Lazy loading doesn't work - jpa - jpa

Hy all,
I have three tables Child, Pet and Toy. Pet has a reference key the child id, and a toy has a reference key to a dog id.
I want to load all data about a Child and his pets, but i don't want to load the toy data
#OneToMany(mappedBy = "petEntity", fetch = FetchType.LAZY)
public Set<PetEntity> getPetEntitySet() {
return petEntitySet;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ChildId", insertable = false, updatable = false)
public ChildEntity getChildEntity() {
return childEntity;
}
same for set of toys.
to load data i have
List<ChildEntity> list = entityManager.createQuery(
"SELECT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;
but this thing loads me all data, not just the information about child and his pets.
How can i suppress the entity manager to load only the data in the table, and not to make joins when i don't want that
Thanks for your advices
i forgot to mention that in this chase it doesn't only load all data, but it returns more than just one copy of a child elemnt

The reason why it is loading more data is because you are using the keyword fetch. This will EAGERLY load whatever the child's pets.
Just remove the fetch and lazy loading will occur.
Update
I see that this is actually what you want, so just ensure equals() and hashCode() is correctly overriden in all relevant classes, as I assume you are using Set.
In this way, jpa knows how to look for duplicates
Second update
Yes you can easily use DISTINCT in your queries.
Just add DISTINCT in your select
List<ChildEntity> list = entityManager.createQuery(
"SELECT DISTINCT c FROM ChildEntity c "+
"LEFT JOIN FETCH c.petEntitySet ",
ChildEntity.class).getResultList();
return list;

Related

EclipseLink ManyToOne - CriteriaBuilder Generated Query is Wrong

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));

JPQL Filtering child

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
}

Constraint the number of rows returned by JOIN FETCH in JPA

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.

Correct way to statelessly update a one-to-many relationship in JPA?

I have a REST interface for a datamodel that has several one-to-many and many-to-many relationships between entities. While many-to-many relationships seem easy to manage statelessly, I'm having trouble with one-to-many. Consider the following one-to-many relationship:
Employee:
#ManyToOne
#JoinColumn(name = "Company_id")
private Company company;
Company:
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Employee> employees = new HashSet<Employee>();
When a company is updated, its employee collection may have been updated as well (employees removed or added) but since the REST interface only allows updating the company as a whole, I cannot explicitly delete or add employees.
Simply replacing the collection does not work, but I found that this seems to work:
public void setEmployees(Set<Employee> employee) {
this.employees.clear(); // magic happens here?
this.employees.addAll(employees);
for (Iterator<Employee> iterator = employees.iterator(); iterator.hasNext();) {
Employee employee = (Employee) iterator.next();
employee.setCompany(this);
}
}
Is this the way it should be done, or is there a better way?
EDIT: In fact the above does not work! It appears to work at first, but then it will break with:
Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
I assume this happens because the db already contains a set of employees and if any of the "old" employees are also part of the replacement set, they collide with the ones in the database.
So what is the right way to replace the set?
First make sure equals is implemented properly. As per hibernate spec: http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode
I had a similar problem doing a merge. Essentially I had to fetch the existing employees associated with the company. I had to merge any changes to existing employees, and then add any new employees.
Query query = em.createQuery("select e from Employee e where e.company = '" + company.getId() + "'");
Collection<Employee> existingEmployees = new LinkedList<Employee>();
try{
Iterables.addAll(existingEmployees, (Collection<Employee>) query.getResultList());
}
catch(NoResultException nre){
//No results
}
for(Employee existingEmployee : existingEmployees){
for(Employee employee : company.getEmployees()){
if(existingEmployee.name().equals(employee.name())){
employee.setId(existingEmployee.getId());
}
employee.setCompany(company);
}
}
i think you have no better choice then to replace the existing collection and simply set the new one provided by the REST response.

JPA OneToMany relations and performace

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