Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate OneToOne automatic join fetching (resolving n+1 problem)

we have a n+1 select problem with Hibernate 3.3.

For simplicity's sake, I'll just do a short abstract example.

Suppose we have the following simple classes:

class MainEntity {
  @Id
  public Long id; //we have a table generator create this id

  @OneToOne ( mappedBy ="main" )
  public SubEntity subEntity;
}

class SubEntity {
 @Id
 @Column( name = "mainId" ) //note that this is the same column as the join column below
 public Long mainId; //in order to have the exact same id as the corresponding MainEntity

 @OneToOne ( fetch = FetchType.LAZY )
 @JoinColumn ( name = "mainId", insertable = false, updatable = false, nullable = false )
 public MainEntity main; //this is used for navigation and queries (" ... subentity.main = :x")
}

So as you can see SubEntity has a relation to MainEntity that is expressed by two properties, where the mainId property is the one responsible for managing the relation/foreign key.

This works quite well and perfectly fits our needs.

However, there's one problem with eagerly loading the SubEntity along with the MainEntity.

Suppose I have a query that returns a collection of MainEntity. With the current setup, Hibernate will issue n + 1 selects: the query itself + n selects for each SubEntity.

Of course I could add a join fetch to the query, but I'd rather like Hibernate to do that automatically. Thus I tried adding @Fetch( FetchMode.JOIN ), but that didn't do anything.

I would also have no problem using @Fetch( FetchMode.SUBSELECT ), which should reduce the select statements to 2 - the original query and a select for the sub entities (at least that's what happens on another property annotated with @CollectionOfElements and @Fetch( FetchMode.SUBSELECT )).


So the question is: how would I tell Hibernate to automatically join fetch or use a single select in order to eagerly load the sub entities? Am I missing something?

Thanks in advance,

Thomas

PS: One thing that might be a problem might be the mappedBy = "main" which doesn't reference the actual id column, but I can't change it to mappedBy = "id".

like image 750
Thomas Avatar asked Aug 11 '11 12:08

Thomas


People also ask

How can we overcome n 1 problem in Hibernate?

Hibernate N+1 issue occurs when you use `FetchType. LAZY` for your entity associations. Hibernate will perform n-additional queries to load lazily fetched objects. To escape this issue use join fetch, batching or sub select.

How do you fix N 1 problems?

The solution to fix the N+1 queries is to configure Hibernate to eagerly fetch the data needed in each query. As I explained before, the best practice is to configure every entity's relationship (ManyToOne…) to be lazily fetched by default.

What do you understand about the N 1 Select problem in Hibernate?

What is the N+1 query problem. The N+1 query problem happens when the data access framework executed N additional SQL statements to fetch the same data that could have been retrieved when executing the primary SQL query. The larger the value of N, the more queries will be executed, the larger the performance impact.

What are the different fetching ways in Hibernate?

Hibernate defines the following fetching strategies: Join fetching: Hibernate retrieves the associated instance or collection in the same SELECT , using an OUTER JOIN . Select fetching: a second SELECT is used to retrieve the associated entity or collection.


2 Answers

If you want to shared primary keys between MainEntity and SubEntity use PrimaryKeyJoinColumn and MapsId annotation.

By using PrimaryKeyJoinColumn the entity is loaded by joining the MainEntity table with the SubEntity table using the same primary key. It should resolve the n+1 problems.

The MapsId annotation ask Hibernate to copy the identifier from another associated entity in our example will copy the SubEntity.mainEntity.id to SubEntity.id.

@Entity
public class MainEntity {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "main_Id")
    private Long id;

    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    private SubEntity  subEntity ;
}


@Entity
public class SubEntity 
{
    @Id @Column(name="main_Id_FK") Long id;

    @MapsId 
    @OneToOne
    @JoinColumn(name = "main_Id_FK")    
    @PrimaryKeyJoinColumn
    private MainEntity mainEntity;        

}

Hibernate Reference Documentation:

PrimaryKeyJoinColumn
MapsId

like image 178
Joel Hudon Avatar answered Oct 16 '22 13:10

Joel Hudon


There are three options to avoid the questions n +1:

 Lot size

 subselect

 Make a LEFT JOIN in the query

Here FAQ1 Here FAQ2

like image 44
Nook Avatar answered Oct 16 '22 13:10

Nook