Is it somehow possible to create a criteria query that performs an outer join on another entity if that entity is not mapped?
I know that an inner join is possible when you do a cross join and add the join condition manually. It would look like this:
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Car> car = cq.from(Car.class);
Root<Color> color = cq.from(Ccolor.class);
cq.where(cb.equal(car.get("colorUuid"), color.get("uuid")));
However I need the behaviour of an outer join in my case.
So let's say I have these entities:
class Car {
#Column(name="color_uuid")
private String colorUuid;
}
class Color {
private String uuid;
private String name;
}
Lets say Color is optional and that's why I need an outer join. The SQL would look like
SELECT * from car LEFT OUTER JOIN color ON car.color_uuid = color.uuid;
Can I do this with Criteria?
You can’t do this with criteria api without making the entities in relation, I’ve faced the same problem as you. Also a cross join can’t help. What I can suggest is:
make a view with the left outer join on the database and then map the view as an entity
make a jpql query
make a native query
I suggest you change the classes in order to have a relationship that logically already exists.
class Car {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "color_uuid", referencedColumnName = "uuid")
private Color color;
}
class Color {
private String uuid;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "color")
private List<Car> cars;
}
Then you can build the left join using criteria:
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> root = criteriaQuery.from(Car.class);
root.join("color", JoinType.LEFT);
List<Car> cars = em.createQuery(criteriaQuery).getResultList();
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.
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
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.