ManyToOne doing multiple queries to fetch records - jpa

I have two entities, and dept_id is the foreign key here.
public class Student implements Serializable {
...
#Id
#Column(name="id")
private Integer id;
#ManyToOne
#JoinColumn(name = "dept_id")
private Department department;
...
}
and
public class Department implements Serializable {
...
#Id
#Column(name="id")
private Integer id;
#Column(name = "name")
private String name;
...
}
Now I am doing the following JPQL where I have around 100 parameters inside in query:
select o from Student o where o.id in(1,2,7,9,15,16, ...)
When I see the JPA log, I found it is fetching 100 records from the Student by one query. After that it is doing 100 separate queries to fetch the Department for each Student. So far my understanding is the I/O operation should be slow. Is there any way so that it fetches everything by a single query?

I found this worked for me:
query.setHint("eclipselink.join-fetch", "o.department");
Also, I found this one is handy as it does not make any joining, but fetches the records separately in a bulk.
query.setHint("eclipselink.batch", "o.department");

Related

Crieria API query using criteriabuilder.construct with a non existing relationship

Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")

Recommended way to solve common requirements of a many-to-many relation with Spring Data

Aim of this question is to share our experience on how to solve the (i think common) requirements with Spring Data.
Given the following many-to-many relationship:
#Entity
public class Book {
#GeneratedValue
#Id
private int id;
#OneToMany(mappedBy = "book", fetch = FetchType.LAZY)
private List<BookCategory> categories;
// rest omitted for clarity
}
#Entity
public class Category {
#GeneratedValue
#Id
private int id;
// rest omitted for clarity
}
#Entity
public class BookCategory {
#GeneratedValue
#Id
private int id;
#ManyToOne(fetch = FetchType.EAGER)
private Book book;
#ManyToOne(fetch = FetchType.EAGER)
private Category category;
int ordering = 0;
// rest omitted for clarity
}
Search requirements:
get all books
get books with category A and B (number of categories to search for is dynamic) order by BookCategory.ordering
get books with category B order by Book.name
When the book is used in the application, the assigned categories need to be always shown.
My questions are
which query building mechanism solves the requirements best? (#Query, Query by example, direct EntityManager usage, ...)
how does the search query look like?
how to avoid the n+1 problem when using the book?
which query building mechanism solves the requirements best?
Use Spring Data's Specification or use QueryDSL library
how does the search query look like?
SELECT b.* from Book where exists (select 1 from bookCategory bc where bc.book_id=b.id and bc.category_id in (:catList) group by bc.book_id having count(bc.book_id) = (:catListSize));
how to avoid the n+1 problem when using the book?
use eager fetch book -> BookCategory

JPA Error joining table and view

I need to join a table and a view in a JPA query. The query won't compile because the view columns can't be identified.
Any suggestions are greatly appreciated.
Updated with parent entity and consistent naming
The query is:
select count(m.id)
from MultiSpeedMotor m,
MultiSpeedQuery q1
where m.id = q1.motorId
and q1.power = 10
The errors are:
The state field path 'q1.motorId' cannot be resolved to a valid type.
The state field path 'q1.power' cannot be resolved to a valid type.
I am working with a legacy database that has a denormalized table similar to this
Long motorId
Long id
Double hi_power
Double lo_power
I have used a view with a union query to normalize this table into
Long motorId
Long id
Long hi
Double power
To model the view of union query in JPA, I have used an #IdClass
public class MultiSpeedQueryId implements Serializable {
private static final long serialVersionUID = -7996931190943239257L;
private Long motorId;
private Long id;
private Long hi;
...
}
#Entity
#Table(name = "multi_speed_query")
#IdClass(MultiSpeedQueryId.class)
public class MultiSpeedQuery implements IMultiSpeedQuery {
#Id
#Column(name = "motor_id")
private Long motorId;
#Id
private Long id;
#Id
private Long hi;
private Double power;
...
}
The parent Entity is mapped as:
#Entity
#Table(name = "multi_speed_motor")
public class MultiSpeedMotor implements Serializable, IMultiSpeedMotor {
private static final long serialVersionUID = 3019928176257499187L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
The query is correct as written.
You CAN join Entities with no pre-defined relationship by using the syntax.
where a.id = b.joinField
The issue was much simpler. I missed part of the JPA error log that was telling the real problem.
The abstract schema type 'MultiSpeedQuery' is unknown.
Once I added the Entity to the persistence.xml, the query, as originally written, worked perfectly.

efficient JPQL: update entities in #onetomany

i have two entities Customer and Order (trivial setters and getters excluded)
#Entity
public class Customer {
#GeneratedValue
#Id
private int id;
#OneToMany
List<Order> orderList;
}
#Entity
public class Order {
#GeneratedValue
#Id
private int id;
#ManyToOne
Customer customer;
private boolean paid;
public Order(Customer customer) {
this.customer = customer;
customer.getOrderList().add(this)
}
}
Now i want to set 'paid = true' for all the orders of a given customer
Below query seem to do the trick, but I get a feeling it is innefficient and the fact that i stored the reverse relationship in Customer.orderList hints that there should be some other way to do this.
UPDATE Order o SET o.paid = true WHERE EXISTS
(SELECT c.orderList FROM Customer c WHERE o MEMBER OF c.orderList AND c = :customer)
I'm using container managed transactions, glassfish and javaDb. But I'd prefer if improvements could be done in JPA/JPQL domain and not specific to container or db.
private id; ?? missed field type
Add to #OneToMany annotation,cascade = CascadeType.All
Customer entity = entityManager.find(Customer.class, id)
for (Order order : entity.getOrderList())
{
order.setPaid(true);
}
if you are using cantainer managed transaction then true will be saved to DB

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,