i have a table and it associated to another table as one to one . In my service class i am calling findById(id).
#Entity
#Table(name = "CRL_EC")
public class LoanOrder {
#OneToOne(fetch = FetchType.EAGER ,cascade = CascadeType.ALL)
#JoinColumn(name = "loan_id" , referencedColumnName = "fLoanId" ,insertable = false ,updatable = false)
LoanEc loanEc;
}
#Entity
#Table(name = "LOAN_EC")
public class LoanEc {
#Id
Long fLoanId;
#OneToOne
#JoinColumn(name = "fLoanId",referencedColumnName = "loan_id" )
LoanOrder loanOrder;
}
public interface ECRepository extends Repository<LoanOrder,Long>{
void save(LoanOrder loanOrder);
}
When I am calling findById(id) through my ECRepository hibernate calling it as separate queries.
In console I see the queries as
select * from LoanOrder where loan_id = ?
select * from LoanEc where fLoanId = ?
and the result is only if the id existss in second table(LoanEc). My expectation is
select * from LoanOrder left outer join LoanEc on loan_id = ?
Why its not associating these two entities ?
Hibernate doesn't do a join because there is a cycle in your entity graph and before Hibernate 6.0, the cycle is stopped as early as possible. In 6.0 we changed that to stop at the later i.e. you would get the left outer join.
I don't know what you mean by "associating these two entities" or "the result is only if the id exists", but if LoanEc is the only attribute in LoanOrder, or part of the id, then the entity won't be materialized if no LoanEc exists for a LoanOrder.
Related
I have an entity (named Parent) with a #OneToOne mapping to a Child entity. Currently defined with FetchType.EAGER, but it doesn't matter to the problem at hand here.
I am trying to perform a query on Parent that does a LEFT JOIN on the Child entity, rendering the FetchType setting useless (supposedly).
However, the query on the Child entity still gets executed, even though the join is performed correctly.
#Entity
public class Parent {
#NotFound(action = NotFoundAction.IGNORE)
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Child child;
}
#Entity
public class Child {
}
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<Parent> criteriaQuery = criteriaBuilder.createQuery(Parent.class);
Root<Parent> root = criteriaQuery.from(Parent.class);
Fetch<Parent, Child> join = root.fetch("child", JoinType.LEFT);
return session.createQuery(criteriaQuery).getSingleResult();
I would have expected this to generate a single query with a LEFT JOIN that gathers all of the data, but instead it performs:
1) SELECT * FROM parent LEFT JOIN child ...
2) SELECT * FROM child where id = ?
Actually your criteria generates the correct query (1). But when Parent entity gets materialized, Hibernate issues the (2) query because the Child should be loaded eagerly. In other words Hibernate will always try to load the child.
There are 2 ways to escape this:
remove EAGER fetch.
change criteria to return a projection
I think you should add the annotation #Fetch(FetchMode.JOIN) on the child field. JoinType just defines the type of the join, but you should tell hibernate that when loading parent, load the child (here eagerly) using join and not using extra selects.
This way you dont need to specify the join in the criteria query, you can just load parents and hibernate will load child entities using the FetchMode.JOIN
OK, the solution was eventually to have a Bi-Directional association. Defining both of the directions solved the issue for me - like this:
#Entity
public class Parent {
#OneToOne(mappedBy="parent")
#Fetch(FetchMode.JOIN)
private Child child;
}
#Entity
public class Child {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Parent parent;
}
Thanks for the help nevertheless:)
I am using Hibernate 5.1.2
I have run into an unexpected problem that I can't seem to work around. Here's the summary of my data model:
dfip_project_version is my superclass table, and dfip_appln_proj_version is my subclass table. dfip_application contains a list of dfip_appln_proj_versions.
I have mapped this as follows:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProjectVersion {
#Id #GeneratedValue
#Column(name = "PROJECT_VERSION_OID")
Long oid;
#Column(name = "PROJ_VSN_EFF_FROM_DTM")
Timestamp effFromDtm;
#Column(name = "PROJ_VSN_EFF_TO_DTM")
Timestamp effToDtm;
#Column(name = "PROJECT_VERSION_TYPE")
#Type(type = "project_version_type")
ProjectVersionType projectVersionType;
}
#Table(name = "DFIP_APPLN_PROJ_VERSION")
#Entity
class ApplicationProjectVersion extends AbstractProjectVersion {
#OneToOne
#JoinColumn(name = "APPLICATION_OID", nullable = false)
Application application;
public ApplicationProjectVersion() {
projectVersionType = ProjectVersionType.APPLICATION;
}
}
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
List<ApplicationProjectVersion> applicationVersions = [];
}
I am using the #Where annotation so that only the current ApplicationProjectVersion is retrieved with the Application.
The problem with this is that Hibernate assumes that the column I am referencing is in the dfip_appl_proj_version table, when it's actually on the super-class table (dfip_project_version).
Here's what I tried so far to work around this limitation:
Attempt 1
I tried putting the #Where annotation onto the AbstractProjectVersion super-class, like so:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
public abstract class AbstractProjectVersion {
...etc...
}
This did nothing, as the WHERE clause does not seem to be noticed when retrieving the Application.
Attempt 2
I made the applicationVersions list on Application LAZY, and tried to map latestVersion manually like this:
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
List<ApplicationProjectVersion> applicationVersions = [];
#ManyToOne
#JoinColumnsOrFormulas([
#JoinColumnOrFormula(formula = #JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "(select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = APPLICATION_OID and pv.PROJ_VSN_EFF_TO_DTM is null)", referencedColumnName="PROJECT_VERSION_OID")),
])
ApplicationProjectVersion latestVersion;
}
This caused Hibernate to generate the following SQL (snippet):
from DFIP_APPLICATION this_
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_
on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and
(select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv
where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = this_.APPLICATION_OID
and pv.PROJ_VSN_EFF_TO_DTM is null)=applicatio2_.PROJECT_VERSION_OID
which resulted in ORA-01799: a column may not be outer-joined to a subquery.
If I can't specify a sub-query in my join formula, then I cannot join to the super-class manually...
Attempt 3
I noticed that usage of #JoinFormula makes Hibernate notice my #Where annotation on the super-class. So I tried the following:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
public abstract class AbstractProjectVersion {
...etc...
}
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
List<ApplicationProjectVersion> applicationVersions = [];
#ManyToOne
#JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID")
ApplicationProjectVersion latestVersion;
}
This generated the following SQL (snippet):
from DFIP_APPLICATION this_
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_
on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and ( applicatio2_1_.PROJ_VSN_EFF_TO_DTM is null)
left outer join DFIP_PROJECT_VERSION applicatio2_1_ on applicatio2_.PROJECT_VERSION_OID=applicatio2_1_.PROJECT_VERSION_OID
This is almost correct! Unfortunately it is not valid SQL, since applicatio2_1_ is used before it is declared on the next line :(.
Now I am out of ideas, so any help would be appreciated. Is there a way to specify a WHERE clause that will bring in only the current ProjectVersion, without getting rid of my inheritance structure?
Related Hibernate issue ticket
I have a solution to this problem. I must admit, it ended up being a little more cumbersome than what I hoped for, but it does work quite well. I waited a couple of months before posting, to make sure that there are no issues and so far, I have not experienced any problems.
My entities are still mapped exactly as described in the question, but instead of using the problematic #Where annotation, I had to use #Filter annotation instead:
public class Application {
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER)
#Cascade([SAVE_UPDATE, DELETE, MERGE])
#Fetch(FetchMode.SELECT)
// Normally we'd just use the #Where(clause = "PROJ_VSN_EFF_TO_DTM is null"), but that doesn't work with collections of
// entities that use inheritance, as we have here.
//
// Hibernate thinks that PROJ_VSN_EFF_TO_DTM is a column on DFIP_APPLN_PROJ_VERSION table, but it is actually on the "superclass"
// table (DFIP_PROJECT_VERSION).
//
// B/c of this, we have to do the same thing with a Filter, which is defined on AbstractProjectVersion.
// NOTE: This filter must be explicitly enabled, which is currently achieved by HibernateForceFiltersAspect
//
#Filter(name="currentProjectVersionOnly",
condition = "{pvAlias}.PROJ_VSN_EFF_TO_DTM is null",
deduceAliasInjectionPoints=false,
aliases=[ #SqlFragmentAlias(alias = "pvAlias", table = "DFIP_PROJECT_VERSION") ]
)
List<ApplicationProjectVersion> projectVersions = [];
}
Since we are using a Filter, we must also define it:
// NOTE: This filter needs to be explicitly turned on with session.enableFilter("currentProjectVersionOnly");
// This is currently achieved with HibernateForceFiltersAspect
#FilterDef(name="currentProjectVersionOnly")
#Table(name = "DFIP_PROJECT_VERSION")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProjectVersion {
}
And of course, we must enable it, since Hibernate does not have a setting to automatically turn on all filters.
To do this I created a system-wide Aspect, whose job is to enable specified filters before every call to any DAO:
/**
* Enables provided Hibernate filters every time a Hibernate session is openned.
*
* Must be enabled and configured explicitly from Spring XML config (i.e. no auto-scan here)
*
* #author Val Blant
*/
#Aspect
public class HibernateForceFiltersAspect {
List<String> filtersToEnable = [];
#PostConstruct
public void checkConfig() throws Exception {
if ( filtersToEnable.isEmpty() ) {
throw new IllegalArgumentException("Missing required property 'filtersToEnable'");
}
}
/**
* This advice gets executed before all method calls into DAOs that extend from <code>HibernateDao</code>
*
* #param jp
*/
#Before("#target(org.springframework.stereotype.Repository) && execution(* ca.gc.agr.common.dao.hibernate.HibernateDao+.*(..))")
public void enableAllFilters(JoinPoint jp) {
Session session = ((HibernateDao)jp?.getTarget())?.getSession();
if ( session != null ) {
filtersToEnable.each { session.enableFilter(it) } // Enable all specified Hibernate filters
}
}
}
And the corresponding Spring configuration:
<!-- This aspect is used to force-enable specified Hibernate filters for all method calls on DAOs that extend HibernateDao -->
<bean class="ca.gc.agr.common.dao.hibernate.HibernateForceFiltersAspect">
<property name="filtersToEnable">
<list>
<value>currentProjectVersionOnly</value> <!-- Defined in AbstractProjectVersion -->
</list>
</property>
</bean>
And there you have it - polymorphic #Where clause :).
since you are looking for #Where with inheritance, I assume you are trying to apply some SQL logic globally, maybe hibernate interceptor or SQL inspector would be a better fit for this type of requirement
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 am writing a JPQL query (with Hibernate as my JPA provider) to fetch an entity Company and several of its associations. This works fine with my "simple" ManyToMany associations, like so:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " + <-- #ManyToOne
"LEFT JOIN FETCH c.acknowledgements " + <-- #ManyToMany
"LEFT JOIN FETCH c.industries " + <-- #ManyToMany
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Hibernate creates a single query to fetch the above, which is good. However, my Company entity also has a many-to-many association with data stored in the intermediate table, hence why this is mapped as #OneToMany and #ManyToOne associations between three entities.
Company <-- CompanyService --> Service
These are the three entities that I have in my code. So a Company instance has a collection of CompanyService entities, which each has a relation to a Service instance. I hope that makes sense - otherwise please check the source code at the end of the question.
Now I would like to fetch the services for a given company by modifying the above query. I read in advance that JPA doesn't allow nested fetch joins or even aliases for joins, but that some JPA providers do support it, and so I tried my luck with Hibernate. I tried to modify the query as such:
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company { ... }
Now, instead of creating a single query, Hibernate creates the following queries:
#1
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
[...]
left outer join company_service companyser6_ on company0_.id = companyser6_.company_id
left outer join service service7_ on companyser6_.service_id = service7_.id
where company0_.id = ?
#2
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
where company0_.id = ?
#3
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
#4
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?
Query #1
I left out the irrelevant joins as these are OK. It appears to select all of the data that I need, including the services and the intermediate entity data (CompanyService).
Query #2
This query simply fetches the company from the database and its City. The city association is eagerly fetched, but the query is still generated even if I change it to lazy fetching. So honestly I don't know what this query is for.
Query #3 + Query #4
These queries are looking up Service instances based on ID, presumably based on service IDs fetched in Query #1. I don't see the need for this query, because this data was already fetched in Query #1 (just as the data from Query #2 was already fetched in Query #1). Also, this approach obviously does not scale well if a company has many services.
The strange thing is that it seems like query #1 does what I want, or at least it fetches the data that I need. I just don't know why Hibernate creates query #2, #3 and #4. So I have the following questions:
Why does Hibernate create query #2, #3 and #4? And can I avoid it?
Does Hibernate support nested association fetching even though JPA doesn't? If so, how would I go about it in my case?
Is this behavior normal, or is it because what I am trying to do is just not supported, and therefore I get weird results? This would seem odd, because query #1 looks perfectly fine
Any pointers of mistakes or alternative solutions to accomplish what I want would be much appreciated. Below is my code (getters and setters excluded). Thanks a lot in advance!
Company entity
#Entity
#Table(name = "company")
#NamedQueries({
#NamedQuery(
name = "Company.profile.view.byId",
query = "SELECT c " +
"FROM Company AS c " +
"INNER JOIN FETCH c.city AS city " +
"LEFT JOIN FETCH c.acknowledgements " +
"LEFT JOIN FETCH c.industries " +
"LEFT JOIN FETCH c.companyServices AS companyService " +
"LEFT JOIN FETCH companyService.service AS service " +
"WHERE c.id = :companyId"
)
})
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
// ...
#ManyToOne(fetch = FetchType.EAGER, targetEntity = City.class, optional = false)
#JoinColumn(name = "postal_code")
private City city;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_acknowledgement", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "acknowledgement_id"))
private Set<Acknowledgement> acknowledgements;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "company_industry", joinColumns = #JoinColumn(name = "company_id"), inverseJoinColumns = #JoinColumn(name = "industry_id"))
private Set<Industry> industries;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private Set<CompanyService> companyServices;
}
CompanyService entity
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
Service entity
#Entity
#Table(name = "service")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#Column(length = 50, nullable = false)
private String name;
#Column(name = "default_description", nullable = false)
private String defaultDescription;
}
Fetching data
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
return query.getSingleResult();
}
Okay, it seems like I figured it out. By setting the fetch type to FetchType.LAZY in CompanyService, Hibernate stopped generating all of the redundant queries that were basically fetching the same data again. Here is the new version of the entity:
#Entity
#Table(name = "company_service")
#IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Service.class)
#JoinColumn(name = "service_id")
private Service service;
#Column
private String description;
}
The JPQL query remains the same.
However, in my particular case with the number of associations my Company entity has, I was getting a lot of duplicated data back, and so it was more efficient to let Hibernate execute an additional query. I accomplished this by removing the two join fetches from my JPQL query and changing my query code to the below.
#Transactional
public Company fetchTestCompany() {
TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
query.setParameter("companyId", 123);
try {
Company company = query.getSingleResult();
Hibernate.initialize(company.getCompanyServices());
return company;
} catch (NoResultException nre) {
return null;
}
}
By initializing the companyServices association, Hibernate executes another query to fetch the services. In my particular use case, this is better than fetching a ton of redundant data with one query.
I hope this helps someone. If anyone has any better solutions/improvements, then I would of course be happy to hear them.
From what you wrote, I would say that nested fetching isn't supported. This is my understanding of your results:
Query #1 is ok, and joins everything that it needs, this is good
However, Query #2 I think gets CompanyService#company (with eager city resulting in inner join City)
Query #3 gets CompanyService#service
Query #4 is a mistery to me
I know this is not an answer, but it might help you understand what's going on in the background.