I'm working with Spring Boot 2.3, Spring Data and Hibernate.
I've the following entities
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
private String name;
#OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Address address;
#Version
private Long version;
}
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class Address {
#Id
private Long id;
private String fullAddress;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
#MapsId
private User user;
#Version
private Long version;
}
When the following code is executed, any query related to the user repository is performed (and for me it is the expected behavior).
Address addressFromDb = addressRepository.findAll().get(0);
log.info("" + addressFromDb.getUser().getId());
// select address0_.id as id1_0_, address0_.full_address as full_add2_0_, address0_.version as version3_0_ from address address0_
but when I execute the following code, then there are multiple queries and I don't understanding why. Apparently the FetchType.LAZY from user to address is not honored.
User userFromDb = userRepository.findAll().get(0);
// select user0_.id as id1_4_, user0_.name as name2_4_, user0_.version as version3_4_ from user user0_
// select address0_.id as id1_0_0_, address0_.full_address as full_add2_0_0_, address0_.version as version3_0_0_ from address address0_ where address0_.id=?
What am I missing?
In order to be more helpful and more clear I've created the following github repo
Hibernate (or more specifically PersistenceContext) needs to know, whether the entity exists or not, so that it can decide, whether to provide a proxy for the entity or null. This does not apply for XToMany relationships, because the whole collection can be wrapped in a proxy and in special case it will be empty.
It is also important to point out, that FetchType is just a suggestion for the JPa implementation and there is no guarantee, that in every case it will be fulfilled. You can read more about #OneToOne here, especially in terms of fetching strategy:
While the unidirectional #OneToOne association can be fetched lazily, the parent-side of a bidirectional #OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad.
Even if the FK is NOT NULL and the parent-side is aware about its non-nullability through the optional attribute (e.g. #OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)), Hibernate still generates a secondary select statement.
For every managed entity, the Persistence Context requires both the entity type and the identifier,
so the child identifier must be known when loading the parent entity, and the only way to find the associated post_details primary key is to execute a secondary query.
Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with #LazyToOne(LazyToOneOption.NO_PROXY) and the child side is not using #MapsId.
Related
I'm trying to make an #OneToOne mapping following the https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ the mapping itself works but its triggering an N+1 query problem.
The query is being made on the parent entity service and its triggering N+1 queries.
How can I improve this code to only make 1 query? We don't need to access the ParentDetails in this case.
EDIT: I've tried using JPQL and LEFT JOIN FETCH ParentDetails and didn't work either.
EDIT2: Just to try to add more information. I've put a breakpoint on the getParentDetails just to make sure I was not calling the getter anywhere and I'm not calling and double-checked and it seems a join problem on the repo call.
Let's go to the code:
Parent
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "parent")
private ParentDetails parentDetails;
// Getters, setters, etc omitted for brevity
}
ParentDetails
#Entity
public class ParentDetails {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
// Getters, setters, etc omitted for brevity
ParentDetailsRepository
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {
Page<Parent>findByNameOrderByName(#Param("name") final String name,final Pageable pageable);
}
Hibernate executes the additional queries because the Parent entity doesn't map the foreign key column. Hibernate doesn't support lazy fetching for that end of the association. When Hibernate instantiates a Parent object, it needs to check if it needs to initialize the association with a proxy or a null object. And at some point, the team decided that they would fetch the associated entity if they are forced to perform a query anyways. I explained that in more detail here: https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one
If you want to avoid the additional queries, you need to model a unidirectional association between ParentDetails and Parent. In your example, that would mean that you need to remove the parentDetails attribute from your Parent entity.
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getters, setters, etc omitted for brevity
}
Because your ParentDetails entity uses the same id value as the Parent entity, you don't need a bidirectional association. If you want to get the ParentDetails entity for a Parent entity, you can get it with a call of the em.find(...) method
Parent p = // fetch the parent object ...
ParentDetails pd = em.find(p.getId(), ParentDetails.class);
I have tables:
users (id, name, email, password)
user_statuses (user_id, is_premium, is_advanced, user_rank_id)
user_ranks (id, name, ordinal)
So the relation between User and UserStatus is 1-1, and I have following entity clasess:
#Entity
#Table(name = "users")
#Getter
#Setter
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserStatus status;
}
#Entity
#Table(name = "user_statuses")
#Getter
#Setter
#NoArgsConstructor
public class UserStatus {
#Id
private long id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
private boolean isPremium;
private boolean isAdvanced;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_rank_id")
private UserRank rank;
}
#Entity
#Table(name = "user_ranks")
#Getter
#Setter
#NoArgsConstructor
public class UserRank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private int ordinal;
}
Then i created endpoint "/users/{id}" which should return user's email address as a string:
#GetMapping("/users/{id}")
public String getUserEmail(#PathVariable("id") long userId) {
User user = service.getUser(userId);
return user.getEmail();
}
When I call above endpoint I get user's email address as a response, however looking at the console log I see that hibernate executed 2 queries but noone asked him to do so:
First one for fetching the user:
SELECT
user0_.id AS id1_2_0_,
user0_.email AS email2_2_0_,
user0_.name AS name3_2_0_,
user0_.password AS password4_2_0_
FROM
users user0_
WHERE
user0_.id = 1;
And second one for fetching User Status that is associated with this User object:
SELECT
userstatus0_.user_id AS user_id1_1_0_,
userstatus0_.is_advanced AS is_advan2_1_0_,
userstatus0_.is_premium AS is_premi3_1_0_,
userstatus0_.user_rank_id AS user_ran4_1_0_
FROM
user_statuses userstatus0_
WHERE
userstatus0_.user_id = 1;
So I am confused: Why is hibernate running second query when I set fetch = FetchType.LAZY on each relation... It looks like that LAZY is ignored for #OneToOne annotation?
I do not use EntityGraph.
How to stop hibernate for running second query?
EDIT
So, it turns out Hibernate ignores my Lazy hint because it needs to decide should it initialize property with NULL or ProxyObject which makes sense. This link explains it well:
https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/
However this link also suggests that the best way to model this is Unidirectional One to One and it says that I can always fetch UserStatus based on User's ID (because both tables "shares" primary key)
However this confuses me a little bit, because I can fetch both rows using single query (SELECT * FROM users LEFT JOIN user_statuses ON users.id = user_statuses.user_id), but with approach described in the link I need 2 queries, and as far as I know (which I might be wrong) is 1 query is better than executing 2 queries, also if I want to fetch 25 users and their User Statuses, then I would also need 2 queries, one for fetching users and then fetching corespoinding user statuses and finally write nested for each loops to join these objects. I could have just executed one single query to fetch everything...
It is possible to make OTO lazy even if it's not the owning side. You just need to mark it as optional = false. This way Hibernate will know that it can safely a create proxy (and null is not possible) as the association always exists. Note, though it really must be non-optional - the 2nd entity must always exist. Otherwise you'll get an exception once Hibernate tries to load it lazily.
As for the number of queries, with native Hibernate (not JPA!) you can select org.hibernate.annotations.FetchMode. Which gives options to:
Use a separate select
Or use a join to load association
Alternatively, you can stay with JPA and write a JPQL query and use fetch join to keep it as a single query.
PS: before doing additional select Hibernate will check if the element already exists within the Session. If it is, then no select is going to be issued. But with fetch join or FetchMode.JOIN you won't have this luxury - join will always happen.
For one to one relation in hibernate it is always loading reference object whether you keep Fetch type Lazy or Eager. So alternate solution is select only those columns which are needed, it should not contain that reference column. So in this case hibernate will not fire another query.
Query for below class will be :
#Query("select new Example(id,field1) from Example")
#Entity
#Table(name = "example")
class Example implements Serializable {
private static final long serialVersionUID = 1L;
public Example(Long id, String field1) {
this.id = id;
this.field1 = field1;
}
#Id
#Column(name = "id", nullable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "example", fetch = LAZY, cascade = ALL)
private CustomerDetails customerDetails;
#Column(name = "field1", nullable = false, updatable = false)
private String field1;
}
I have an Employee and Address with one-to-one bi-directional mapping:
#Entity
public class Employee {
#Id
#Column(name = "EMP_ID")
private long id;
private String firstName;
private String lastName;
private double salary;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ADDRESS_ID")
private Address address;
}
Below is my address entity:
#Entity
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private long id;
private String street;
private String city;
private String province;
private String country;
private String pinCode;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "address")
private Employee owner;
}
In Address I have set Fetch type as Lazy. So if I get an address then I am expecting hibernate to run select query on address only, but I see in logs that it is trying to get Employee also.
Below is my HQL query:
List<Address> emps = session.createQuery("from Address where id=20").list();
These are the queries run by Hibernate:
Hibernate:
/*
from
Address
where
id=20 */ select
address0_.ADDRESS_ID as ADDRESS_1_0_,
address0_.city as city2_0_,
address0_.country as country3_0_
from
Address address0_
where
address0_.ADDRESS_ID=20
Hibernate:
/* load Employee */ select
employee0_.EMP_ID as EMP_ID1_1_0_,
employee0_.ADDRESS_ID as ADDRESS_5_1_0_,
employee0_.firstName as firstNam2_1_0_,
employee0_.lastName as lastName3_1_0_
from
Employee employee0_
where
employee0_.ADDRESS_ID=?
Why hibernate loads Employee eagerly even when I set its fetching strategy as LAZY.
This great article describes the problem and a possible solution:
https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Possible solution: It must be a one-directional relationship from child to parent. The parent cannot have a #OneToOne field to access the child because:
"For every managed entity, the Persistence Context requires both the entity type and the identifier, so the child identifier must be known when loading the parent entity, and the only way to find the associated {child} primary key is to execute a secondary query."
Second solution: Use #OneToMany instead. Don't use #OneToOne because it has this complicated, subtle, quirky problem. You can alter the code to only allow one-to-one access and optionally add a unique key to enforce 1-1.
Lazy loading on one-to-one mapping is possible either by
Setting optional=false (If its not nullable) or
JoinColumn (not on PK and might require schema change)
You can refer to this link for moreinfo.
Explanation : You can refer to explanation link for detailed explanation about this.
Newer version of hibernate can't use trick such as optional=false.
Checkout the updated solution best way to map a onetoone relationship
What is the difference between:
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
#JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
private List<Branch> branches;
...
}
and
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY,
mappedBy = "companyIdRef")
private List<Branch> branches;
...
}
The annotation #JoinColumn indicates that this entity is the owner of the relationship (that is: the corresponding table has a column with a foreign key to the referenced table), whereas the attribute mappedBy indicates that the entity in this side is the inverse of the relationship, and the owner resides in the "other" entity. This also means that you can access the other table from the class which you've annotated with "mappedBy" (fully bidirectional relationship).
In particular, for the code in the question the correct annotations would look like this:
#Entity
public class Company {
#OneToMany(mappedBy = "company",
orphanRemoval = true,
fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<Branch> branches;
}
#Entity
public class Branch {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "companyId")
private Company company;
}
#JoinColumn could be used on both sides of the relationship. The question was about using #JoinColumn on the #OneToMany side (rare case). And the point here is in physical information duplication (column name) along with not optimized SQL query that will produce some additional UPDATE statements.
According to documentation:
Since many to one are (almost) always the owner side of a bidirectional relationship in the JPA spec, the one to many association is annotated by #OneToMany(mappedBy=...)
#Entity
public class Troop {
#OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop has a bidirectional one to many relationship with Soldier through the troop property. You don't have to (must not) define any physical mapping in the mappedBy side.
To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and set the many to one #JoinColumn as insertable and updatable to false. This solution is not optimized and will produce some additional UPDATE statements.
#Entity
public class Troop {
#OneToMany
#JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
Unidirectional one-to-many association
If you use the #OneToMany annotation with #JoinColumn, then you have a unidirectional association, like the one between the parent Post entity and the child PostComment in the following diagram:
When using a unidirectional one-to-many association, only the parent side maps the association.
In this example, only the Post entity will define a #OneToMany association to the child PostComment entity:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();
Bidirectional one-to-many association
If you use the #OneToMany with the mappedBy attribute set, you have a bidirectional association. In our case, both the Post entity has a collection of PostComment child entities, and the child PostComment entity has a reference back to the parent Post entity, as illustrated by the following diagram:
In the PostComment entity, the post entity property is mapped as follows:
#ManyToOne(fetch = FetchType.LAZY)
private Post post;
The reason we explicitly set the fetch attribute to FetchType.LAZY is because, by default, all #ManyToOne and #OneToOne associations are fetched eagerly, which can cause N+1 query issues.
In the Post entity, the comments association is mapped as follows:
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
The mappedBy attribute of the #OneToMany annotation references the post property in the child PostComment entity, and, this way, Hibernate knows that the bidirectional association is controlled by the #ManyToOne side, which is in charge of managing the Foreign Key column value this table relationship is based on.
For a bidirectional association, you also need to have two utility methods, like addChild and removeChild:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
These two methods ensure that both sides of the bidirectional association are in sync. Without synchronizing both ends, Hibernate does not guarantee that association state changes will propagate to the database.
Which one to choose?
The unidirectional #OneToMany association does not perform very well, so you should avoid it.
You are better off using the bidirectional #OneToMany which is more efficient.
I disagree with the accepted answer here by Óscar López. That answer is inaccurate!
It is NOT #JoinColumn which indicates that this entity is the owner of the relationship. Instead, it is the #ManyToOne annotation which does this (in his example).
The relationship annotations such as #ManyToOne, #OneToMany and #ManyToMany tell JPA/Hibernate to create a mapping. By default, this is done through a seperate Join Table.
#JoinColumn
The purpose of #JoinColumn is to create a join column if one does
not already exist. If it does, then this annotation can be used to
name the join column.
MappedBy
The purpose of the MappedBy parameter is to instruct JPA: Do NOT
create another join table as the relationship is already being mapped
by the opposite entity of this relationship.
Remember: MappedBy is a property of the relationship annotations whose purpose is to generate a mechanism to relate two entities which by default they do by creating a join table. MappedBy halts that process in one direction.
The entity not using MappedBy is said to be the owner of the relationship because the mechanics of the mapping are dictated within its class through the use of one of the three mapping annotations against the foreign key field. This not only specifies the nature of the mapping but also instructs the creation of a join table. Furthermore, the option to suppress the join table also exists by applying #JoinColumn annotation over the foreign key which keeps it inside the table of the owner entity instead.
So in summary: #JoinColumn either creates a new join column or renames an existing one; whilst the MappedBy parameter works collaboratively with the relationship annotations of the other (child) class in order to create a mapping either through a join table or by creating a foreign key column in the associated table of the owner entity.
To illustrate how MapppedBy works, consider the code below. If MappedBy parameter were to be deleted, then Hibernate would actually create TWO join tables! Why? Because there is a symmetry in many-to-many relationships and Hibernate has no rationale for selecting one direction over the other.
We therefore use MappedBy to tell Hibernate, we have chosen the other entity to dictate the mapping of the relationship between the two entities.
#Entity
public class Driver {
#ManyToMany(mappedBy = "drivers")
private List<Cars> cars;
}
#Entity
public class Cars {
#ManyToMany
private List<Drivers> drivers;
}
Adding #JoinColumn(name = "driverID") in the owner class (see below), will prevent the creation of a join table and instead, create a driverID foreign key column in the Cars table to construct a mapping:
#Entity
public class Driver {
#ManyToMany(mappedBy = "drivers")
private List<Cars> cars;
}
#Entity
public class Cars {
#ManyToMany
#JoinColumn(name = "driverID")
private List<Drivers> drivers;
}
The annotation mappedBy ideally should always be used in the Parent side (Company class) of the bi directional relationship, in this case it should be in Company class pointing to the member variable 'company' of the Child class (Branch class)
The annotation #JoinColumn is used to specify a mapped column for joining an entity association, this annotation can be used in any class (Parent or Child) but it should ideally be used only in one side (either in parent class or in Child class not in both) here in this case i used it in the Child side (Branch class) of the bi directional relationship indicating the foreign key in the Branch class.
below is the working example :
parent class , Company
#Entity
public class Company {
private int companyId;
private String companyName;
private List<Branch> branches;
#Id
#GeneratedValue
#Column(name="COMPANY_ID")
public int getCompanyId() {
return companyId;
}
public void setCompanyId(int companyId) {
this.companyId = companyId;
}
#Column(name="COMPANY_NAME")
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
#OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
public List<Branch> getBranches() {
return branches;
}
public void setBranches(List<Branch> branches) {
this.branches = branches;
}
}
child class, Branch
#Entity
public class Branch {
private int branchId;
private String branchName;
private Company company;
#Id
#GeneratedValue
#Column(name="BRANCH_ID")
public int getBranchId() {
return branchId;
}
public void setBranchId(int branchId) {
this.branchId = branchId;
}
#Column(name="BRANCH_NAME")
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="COMPANY_ID")
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
I'd just like to add that #JoinColumn does not always have to be related to the physical information location as this answer suggests. You can combine #JoinColumn with #OneToMany even if the parent table has no table data pointing to the child table.
How to define unidirectional OneToMany relationship in JPA
Unidirectional OneToMany, No Inverse ManyToOne, No Join Table
It seems to only be available in JPA 2.x+ though. It's useful for situations where you want the child class to just contain the ID of the parent, not a full on reference.
Let me make it simple.
You can use #JoinColumn on either sides irrespective of mapping.
Let's divide this into three cases.
1) Uni-directional mapping from Branch to Company.
2) Bi-direction mapping from Company to Branch.
3) Only Uni-directional mapping from Company to Branch.
So any use-case will fall under this three categories. So let me explain how to use #JoinColumn and mappedBy.
1) Uni-directional mapping from Branch to Company.
Use JoinColumn in Branch table.
2) Bi-direction mapping from Company to Branch.
Use mappedBy in Company table as describe by #Mykhaylo Adamovych's answer.
3)Uni-directional mapping from Company to Branch.
Just use #JoinColumn in Company table.
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
#JoinColumn(name="courseId")
private List<Branch> branches;
...
}
This says that in based on the foreign key "courseId" mapping in branches table, get me list of all branches. NOTE: you can't fetch company from branch in this case, only uni-directional mapping exist from company to branch.
JPA is a layered API, the different levels have their own annotations. The highest level is the (1) Entity level which describes persistent classes then you have the (2) relational database level which assume the entities are mapped to a relational database and (3) the java model.
Level 1 annotations: #Entity, #Id, #OneToOne, #OneToMany, #ManyToOne, #ManyToMany.
You can introduce persistency in your application using these high level annotations alone. But then you have to create your database according to the assumptions JPA makes. These annotations specify the entity/relationship model.
Level 2 annotations: #Table, #Column, #JoinColumn, ...
Influence the mapping from entities/properties to the relational database tables/columns if you are not satisfied with JPA's defaults or if you need to map to an existing database. These annotations can be seen as implementation annotations, they specify how the mapping should be done.
In my opinion it is best to stick as much as possible to the high level annotations and then introduce the lower level annotations as needed.
To answer the questions: the #OneToMany/mappedBy is nicest because it only uses the annotations from the entity domain. The #oneToMany/#JoinColumn is also fine but it uses an implementation annotation where this is not strictly necessary.
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "company_id_ref", referencedColumnName = "company_id")
private List<Branch> branches;
...
}
That Will give below Hibernate logs
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)
Hibernate: update branch set company_id_ref=? where id=?
And
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY,
mappedBy = "company")
private List<Branch> branches;
...
}
That will give below Hibernate logs
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)
We can clearly see that #joinColumn will cause additional update queries.
so you do not need to set parent entity explicitly to child entity,
That we have to do while using mappedBy
to save children with a parent
I have a list of projects and a list of customers. A project can be for one customer and every customer can have many projects. So it's a simple 1:n relationship where the project is the owning side.
Simplified to the essential it is
#Entity
public class Project {
#Id
long id;
#ManyToOne(optional = true)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
}
#Entity
public class Customer {
#Id
long id;
}
When I load a list of projects, I want to retrieve the customers efficiently at the same time. This is not the case. There is one single query for the projects and then for every distinct customer that is encountered a separate query is issued.
So say I have 100 projects that are assigned to 50 different customers. This would result in one query for the projects and 50 queries for the customers.
This quickly adds up and for large project/customer lists our application gets rather slow. Also this is just one example. All our entities with relationships are affected by this behavior.
I already tried #Fetch(FetchMode.JOIN) on the customers field as suggested here but it does nothing and FetchMode.SUBQUERY is not applicable according to Hibernate:
org.hibernate.AnnotationException: Use of FetchMode.SUBSELECT not allowed on ToOne associations
How can I fix this problem?
If you are using Spring Data JPA to implement your repositories, you can specify lazy fetching in the JPA entities:
#Entity
public class Project {
#Id
long id;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
}
#Entity
public class Customer {
#Id
long id;
...
}
And add #EntityGraph to your Spring Data JPA-based repository:
#Repository
public interface ProjectDao extends JpaRepository<Project, Long> {
#EntityGraph(
type = EntityGraphType.FETCH,
attributePaths = {
"customer"
}
)
Optional<Project> findById(Long id);
...
}
My blog post at https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html helps you preventing the N+1 select problem using Spring Data JPA and #EntityGraph.
Yes, it is a by-the-book example of the n+1 selects problem.
The approach I use in most cases is to make the association lazy and define a batch size.
Alternatively, you could use a JPQL query with [left] join fetch to initialize the association directly from the query result set:
select p from Project p left join fetch p.customer
Yes, it is a by-the-book example of the n+1 selects problem as #dragan-bozanovic said.
In Spring-Boot 2.1.3 #Fetch(FetchMode.JOIN) can be used to solve it:
#ManyToOne(optional = true)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "customer", nullable = true, updatable = true)
Customer customer;
Warning: If the relationship can be invalid, for example when marked with #NotFound(action = NotFoundAction.IGNORE), each invalid relationship will trigger another SELECT query.