Hibernate performing too many queries - java

I believe that I'm misunderstanding how subselect and eager works; my goal is to improve performance as I encounter the N+1 problem
Edit I'm wondering whether it would be quicker for me to just use the create SQL query method and create the objects myself although I was hoping hibernate would be on par with performance. I can pull back all of the data required, in the example below, in a single query so why on earth is hibernate doing a separate query for each?
I've created the following test case to highlight my issue, please excuse the crudity of this model..
#Entity
#Table(name = "Area")
public class Area implements Serializable
{
#Id
#GeneratedValue(generator = "areaId" )
#GenericGenerator(name = "areaId", strategy = "uuid2")
public String areaId;
#OneToMany(mappedBy = "area", fetch=FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
public Set<EmployeeArea> employeeAreas = new HashSet<EmployeeArea>();
}
#Entity
#Table(name = "Employee")
public class Employee implements Serializable
{
#Id
#GeneratedValue(generator = "employeeId" )
#GenericGenerator(name = "employeeId", strategy = "uuid2")
public String employeeId;
#OneToMany(mappedBy = "employee", fetch=FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
public Set<EmployeeArea> employeeAreas = new HashSet<EmployeeArea>();
}
#Entity
#Table(name = "EmployeeArea")
public class EmployeeArea implements Serializable
{
#Id
#GeneratedValue(generator = "employeeAreaId" )
#GenericGenerator(name = "employeeAreaId", strategy = "uuid2")
public String employeeAreaId;
#Id
#ManyToOne
public Employee employee;
#Id
#ManyToOne
public Area area;
}
I have then populated with some sample test data: -
Employee employee = new Employee();
Area area = new Area();
EmployeeArea employeeArea = new EmployeeArea();
employeeArea.area = area;
employeeArea.employee = employee;
session.save(employee);
session.save(area);
session.save(employeeArea);
This can be run a few times to provide some data.
I then perform the following: -
session.createQuery("FROM Employee e INNER JOIN e.employeeAreas ea INNER JOIN ea.area").list();
The reason I do the JOIN is so that I can perform specialist searches. I was looking at criteria but it seemed that it didn't allow me to do all that I could with WHERE
I would expect that it would be doing at most 3 queries and 2 sub queries.
SELECT * FROM Employee INNER JOIN EmployeeArea ON condition INNER JOIN Area ON condition
SELECT * FROM Employee WHERE employeeId IN (subquery 1)
SELECT * FROM Area WHERE areaId IN (subquery 2)
In fact, for 6 inputs of the test data aforementioned, I seem to be getting 6 selects for an employee, 6 selects for an area, something that looks like my assumed query for '1.' and then two larger queries that seem just plain wrong: -
select
employeear0_.employee_employeeId as employee2_3_2_,
employeear0_.employeeAreaId as employee1_4_2_,
employeear0_.employee_employeeId as employee2_4_2_,
employeear0_.area_areaId as area3_4_2_,
employeear0_.employeeAreaId as employee1_4_1_,
employeear0_.employee_employeeId as employee2_4_1_,
employeear0_.area_areaId as area3_4_1_,
area1_.areaId as areaId1_0_0_
from
EmployeeArea employeear0_
inner join
Area area1_
on employeear0_.area_areaId=area1_.areaId
where
employeear0_.employee_employeeId in (
select
employee1_.employeeId
from
EmployeeArea employeear0_
inner join
Employee employee1_
on employeear0_.employee_employeeId=employee1_.employeeId
where
employeear0_.area_areaId in (
select
area2_.areaId
from
Employee employee0_
inner join
EmployeeArea employeear1_
on employee0_.employeeId=employeear1_.employee_employeeId
inner join
Area area2_
on employeear1_.area_areaId=area2_.areaId
)
)
then a very similar one for area.
My goal is to be able to use each employee object in the returned list to identify the areas worked in. There would be more fields in each entity however this test case has been simplified.

I solved the problem; it was an issue with my join table. See the following: -
#Id
#ManyToOne
public Employee employee;
#Id
#ManyToOne
public Area area;
I had used #Id which was resulting in the StackOverflowError exception that was being thrown. Using the following query, with a OneToMany fetch of EAGER and #Fetch JOIN on Employee and a OneToMany fetch of LAZY and #Fetch SELECT on Area, I can then perform the following query: -
List<Employee> employees = session.createQuery("FROM Employee e INNER JOIN FETCH e.employeeAreas ea INNER JOIN FETCH ea.area").list();
Whilst being able to use WHERE on the one of the join table columns.

Use JOIN strategy and link Area to EmployeeArea lazily, while Employee eagerly loads EmployeeAreas. When Employee loads EmployeeArea, hibernate session is populated with EmployeeArea objects. Then if you navigate through Employee.EmployeeArea.Area.EmloyeeArea nothing will be get from a database, because we already have EmployeeArea in the session cache.

Related

Hibernate and Criteria Api generates wrong Join condition

I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee

Hibernate deep nested collection restriction not working

I have a quite large and messy datastructure of a questionnaire, which has resulted in a long HQL
select s from Session s join s.questionnaire q join q.categories c join c.questions q2 join q2.answers a where a.session.id=:id2 and s.id=:id
I think that this HQL looks reasonable and the resulting sql as well
select session0_.id as id1_9_, session0_.name as name2_9_, session0_.position as position3_9_, session0_.questionnaire as question4_9_ from session session0_ inner join questionnaire questionna1_ on session0_.questionnaire=questionna1_.id inner join category categories2_ on questionna1_.id=categories2_.parent_questionnaire inner join question questions3_ on categories2_.id=questions3_.parent_category inner join answer answers4_ on questions3_.id=answers4_.question where answers4_.session=? and session0_.id=?
...even if it is quite large
But for some reason it is completely ignoring the answer restriction.
When checking in the debugger I can see 4 different answers for 4 different sessions.
How is this possible?
Edit
OK, this is really interesting...
select s from Session s join s.questionnaire q join q.categories c where c.name=:name and s.id=:id
this is also not working and the categories are not filtered, so I am assuming the mapping between Session and Questionnaire is wrong?
#Entity
#Table(name = "session")
#Cacheable
#org.hibernate.annotations.Cache(region = "session", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Session implements Serializable {
#Id
#Column(name = "id")
private String id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "questionnaire")
#LazyCollection(LazyCollectionOption.FALSE)
private Questionnaire questionnaire;
}
#Entity
#Table(name = "questionnaire")
#Cacheable
#org.hibernate.annotations.Cache(region = "questionnaire", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Questionnaire implements Serializable {
#Id
#Column(name = "id")
private String id;
}
... changing to this doesn't work
select q from Questionnaire q join q.categories c where c.name=:name
so I am completely lost

Criteria Add all the tables from the entity class when it only needs part of it

when i create a count query with hibernate - Criteria - add all the possible table from the entity class as left join which is bad performance .
The entity :
#Entity
#Table(name = "employees")
Public Class Employees {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "lz_job_stat_id")
private Integer id;
#ManyToOne
#JoinColumn(name = "departments_id")
private Departments departments;
#ManyToOne
#JoinColumn(name = "managers_id")
private Managers managers;
}
And the criteria :
public class EmployeeDao {
public List<EmpDao> findIt(){
.....
Criteria crit = createEntityCriteria().setFetchMode("departments", FetchMode.SELECT);
crit.add(Restrictions.eq("managers.deleted", false));
crit.setProjection(Projections.count("id"));
return crit.list();
}
}
And the produced SQL :
select count() as y0_
from employees this_
left outer join departments department3_
on this_.department_id=department3_.department_id
left outer join managers manager2_
on this_.manager_id=manager2_.manager_id
now when i try the crit.list - it create a left join for all the possible tables.
when its not supposed to create a join for all of them.
isnt Criteria smart enought to know i dont need this tables ? only the one i use the "WHERE CLAUSE"
is there a way to explicitly tell Criteria "DO NOT JOIN THIS TABLES !!!"
without SQL
Specify fetch type on ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "departments_id")
private Departments departments;
or IMHO more preferably in criteria:
criteria.setFetchMode("departments", FetchMode.SELECT)

Hibernate: separate sql query for every collection

I have a Person class that has a collection of Contacts. Everything works ok, I get the list of persons with their contacts. However, in log I see that a separate query is made to read collection of every person. That is too bad.
How to make hibernate make a join to read all the data in one query? I use JPA.
This is the person class:
#Entity
#Table(name = "tbl1")
public class PersonItem implements Serializable{
#Id
#Column(name="col1")
private String guid;
.....
#ElementCollection(targetClass = ContactItem.class,fetch=FetchType.EAGER)
#CollectionTable(name="tbl2",joinColumns=#JoinColumn(name="col2"))
private List<ContactItem> contacts;
....
}
This is the contact class
#Embeddable
#Table(name = "tbl2")
public class ContactItem implements Serializable {
#Column(name="col1")
private String guid;
#Column(name="col3")
private String info;
}
This is the way I get the list of persons:
Query query = em.createQuery("Select p from PersonItem p WHERE p.guid IN (:guids)");
query.setParameter("guids", guids);
List<PersonItem> list=query.getResultList();
And this what I see in log (I have three persons in DB):
Hibernate: select personitem0_.col1 as col1_0_, personitem0_.col4 as col2_0_, personitem0_.col2 as col3_0_, personitem0_.col3 as col4_0_ from tbl1 personitem0_ where personitem0_.col1 in (? , ? , ?)
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Hibernate: select contacts0_.col2 as col1_1_0_, contacts0_.col1 as col2_1_0_, contacts0_.col3 as col3_1_0_ from tbl2 contacts0_ where contacts0_.col2=?
Please, begin from a more simple mapping. Use plural names, and column prefixes.
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column(name = "f_guid")
private String guid;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Contact> contacts;
}
#Entity
#Table(name = "contacts")
public class Contact {
#Id
#Column(name = "f_guid")
private String guid;
#Column(name = "f_info")
private String info;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_person")
private Person person;
}
Person is associated to contacts by a foreign key fk_person in the contacts table.
Update
Looks like JPQL overrides a default fetching strategy. You need to specify a fetch explicitly
select p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
If you have duplicates, cause of joins, you can use distinct
select distinct p from PersonItem p left join fetch p.contacts WHERE p.guid IN (:guids)
Try #Fetch on your relation.
Also i would suggest to use #OneToMany relation int this case
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN) //You can use SUBSELECT as well
private List<ContactItem> contacts;
You can read more about fetching strategies here
fetch-“join” = Disable the lazy loading, always load all the collections and entities.
fetch-“select” (default) = Lazy load all the collections and entities.
batch-size=”N” = Fetching up to ‘N’ collections or entities, Not record.
fetch-“subselect” = Group its collection into a sub select statement.

Hibernate Named Query (Select all instances of entity not appearing in other entity)

I have a problem of creating NamedQuery with Hibernate. The problem is I need to select a list of Books not appearing in Orders. My classes looks something like this:
#Entity
#NamedQueries({ #NamedQuery(name = "Book.findAvailable",
query = "SELECT b FROM Book b WHERE b.id not in ????????") })
public class Book {
#Id
#GeneratedValue
private Long id;
......
and Order:
#Entity(name = "orders")
public class Order {
#Id
#GeneratedValue
private Long id;
#ElementCollection
private List<Book> items;
.....
As you see, I keep my Books in order in a list. The Query I need should pull out all the books from the DB which don't apear in any order.
Any help is appreciated. Many thanks.
Try
SELECT b
FROM Book b
WHERE NOT EXISTS (
SELECT o
FROM Order o
WHERE b MEMBER OF o.items
)
to find books for which there is no order such that the book is a member of the order's items list.
(I should note that this is probably not very efficient due to the negations. Flagging Books once they occur in an Order is actually more efficient.)

Categories