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
Related
I am seeing a strange behavior in Hibernate 4.3.5 and 4.3.11, where I have a table with a self join that also load other objects here is my class
#Entity(name = "site")
#Table(name = "site")
public class Site implements Serializable
{
#OneToMany(mappedBy = "site", fetch = FetchType.EAGER)
private List<SiteAttribute> siteAttributes;
#Id
#GeneratedValue
#Column(name = "site_id")
private Integer siteID;
#OneToMany(mappedBy = "site", fetch = FetchType.EAGER)
private List<ContainerPage> containerPages;
#OneToMany(mappedBy = "site", fetch = FetchType.EAGER)
private List<UserTitle> userTitleList;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "parent_site_id")
private Site parentSite;
}
The table as a self join using the parent_site_id, and each site can have multiple son sites.
The code to retrieve the Site object is:
Query q = session.createQuery("from site where site_id=:siteID");
q.setParameter("siteID", currentSiteID.intValue());
s = (Site) q.uniqueResult();
When I open the logs from hibernate the query executed look like this:
select ...
from site site0_
left outer join containers_pages containerp2_ on site0_.site_id=containerp2_.site_id
left outer join containers container3_ on containerp2_.container_id=container3_.container_id
left outer join pages page4_ on containerp2_.page_id=page4_.page_id
left outer join widgets_containers widgetcont5_ on containerp2_.container_page_id=widgetcont5_.container_page_id
left outer join site site6_ on site0_.parent_site_id=site6_.site_id
left outer join site_attribute siteattrib7_ on site6_.site_id=siteattrib7_.site_id
left outer join user_title usertitlel10_ on site6_.site_id=usertitlel10_.site_id where site0_.site_id=?
From the moment we have a left join with site using parent hibernate stops using my site0_.site_id and uses site6.site_id. Is this behavior correct?
Looks like it is missing information for the current object and this select will return much more results than it should.
Is something wrong with my implementation, or do I need to get the Site and do another query to retrieve the parent site?
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)
I have 2 entities with #Where annotation. First one is Category;
#Where(clause = "DELETED = '0'")
public class Category extends AbstractEntity
and it has the following relation;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
private Set<SubCategory> subCategories = Sets.newHashSet();
and second entity is SubCategory;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
and contains corresponding relation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
Whenever I call the following Dao method;
#Query(value = "select distinct category from Category category join fetch category.subCategories subcategories")
public List<Category> findAllCategories();
I got the following sql query;
select
distinct category0_.id as id1_3_0_,
subcategor1_.id as id1_16_1_,
category0_.create_time as create2_3_0_,
category0_.create_user as create3_3_0_,
category0_.create_userip as create4_3_0_,
category0_.deleted as deleted5_3_0_,
category0_.update_time as update6_3_0_,
category0_.update_user as update7_3_0_,
category0_.update_userip as update8_3_0_,
category0_.version as version9_3_0_,
category0_.name as name10_3_0_,
subcategor1_.create_time as create2_16_1_,
subcategor1_.create_user as create3_16_1_,
subcategor1_.create_userip as create4_16_1_,
subcategor1_.deleted as deleted5_16_1_,
subcategor1_.update_time as update6_16_1_,
subcategor1_.update_user as update7_16_1_,
subcategor1_.update_userip as update8_16_1_,
subcategor1_.version as version9_16_1_,
subcategor1_.category_id as categor11_16_1_,
subcategor1_.name as name10_16_1_,
subcategor1_.category_id as categor11_3_0__,
subcategor1_.id as id1_16_0__
from
PUBLIC.t_category category0_
inner join
PUBLIC.t_sub_category subcategor1_
on category0_.id=subcategor1_.category_id
where
(
category0_.DELETED = '0'
)
Could you please tell me why the above query lacks
and subcategor1_.DELETED = '0'
inside its where block?
I have just solved a similar problem in my project.
It is possible to put #Where annotation not only on Entity, but on also on your child collection.
According to the javadoc:
Where clause to add to the element Entity or target entity of a collection
In your case, it would be like :
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
#Where(clause = "DELETED = '0'")
private Set<SubCategory> subCategories = Sets.newHashSet();
Please find a similar issues resolved here
I believe thus solution is not as invasive compared to using Hibernate Filters.These filters are disabled by default and operate on Session level, thus enabling them each time new Session opens is extra work especially when your DAO works through abstractions like Spring Data
This is a quick reply;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
Where will effect when direct query for SubCategry.
To not get deleted sub categories use Hibernate Filters
as exampled on here
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.
I have two entities:
#Entity
#Table(name = "ACCOUNT")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class MyCloudAccount implements Serializable {
...
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<ServerInstance> servers = new HashSet<ServerInstance>();
...
}
#Entity
#Table(name = "SERVER_INSTANCE")
public class ServerInstance implements Serializable {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACCOUNT_ID")
private MyCloudAccount account;
...
}
I am getting all accounts by this code:
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ");
sql.append(persistentClass.getName());
sql.append(" e");
return entityManager.createQuery(sql.toString()).getResultList();
And this produces one query for the account and N queries for the servers instead of one with outer join. How to force JPA to make the query in optimal way?
I find it more convenient to use Java Persistence Query Language
you can do:
#NamedQueries{
#NamedQuery(name="myQuery" query="SELECT a FROM MyCloudAccount JOIN FETCH a.servers")
}
public class MyCloudAccount{
...
}
then you can do
TypedQuery<MyCloudAccount> query = em.createNamedQuery("MyCloudAccount.myQuery", MyCloudAccount.class);
List<MyCloudAccount> results = query.getResultList();
EDIT
You are actually already using JPQL. The key thing to your problem is using the JOIN FECTH command.