Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate OneToOne(optional=true) with FetchMode.JOIN try to re-select null values

My problem is that hibernate eager loading of OneToOne association execute +1 select for each null relation.

Entity example:

@Entity 
class SideBlue {
    @Column(nullable = false)
    private Integer timestamp;

    @OneToOne(optional=true) 
    @JoinColumn(name="timestamp", referenceColumn="timestamp", insertable = false, updatable = false) 
    SideRed redSide; 
}
@Entity 
class SideRed {
    @Column(nullable = false)
    private Integer timestamp;
}

(It's a legacy database schema, so database modifications is not allowed)

Query example:

CriteriaBuilder builder... CriteriaQuery query...
Root<SideBlue> root = query.from(SideBlue.class);
root.fetch(SideBlue_.sideRed, JoinType.LEFT);
entityManager().createQuery(query).getResultList();

The result: If all blue side entities has one red side, everything goes correctly, so hibernate only execute one query to the database for whichever entities will be retrieved.

But, if blue side entities has no red side entity associated, hibernate try to find the other side one more time. Hibernate sql comment says '/* load RedSide */ select ...' for each null redSide property.

How can I skip this second select?

The practical problem appears when latency is not extremely low. If I try to select 1million rows, and 1/3 have null 'red sides', the total latency added is a real problem.

EDIT:

This is the debug log for the query

10:04:32.812 [main] DEBUG org.hibernate.loader.Loader - Result set row: 0
10:04:32.815 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[SideBlue#1269721], EntityKey[SideRed#3620564]
10:04:32.833 [main] DEBUG org.hibernate.loader.Loader - Result set row: 1
10:04:32.833 [main] DEBUG org.hibernate.loader.Loader - Result row: EntityKey[SideBlue#1269776], null

The first row contains the blue and red sides, but the second one only the blue side. So hibernate must know that related red side dont exists. But, after all result rows are processed...

10:04:33.083 [main] DEBUG o.h.engine.internal.TwoPhaseLoad - Resolving associations for [BlueSide#1269721]
10:04:33.084 [main] DEBUG org.hibernate.loader.Loader - Loading entity: [RedSide#component[timestamp]{timestamp=1338937390}]
10:04:33.084 [main] DEBUG org.hibernate.SQL - /* load RedSide */ select ...
! Nothing really loaded because the previous SQL return empty result set, again !
10:04:33.211 [main] DEBUG org.hibernate.loader.Loader - Done entity load
like image 645
Ignacio Baca Avatar asked Nov 13 '22 17:11

Ignacio Baca


1 Answers

Well, you trying to not load SideRed when you do a query for SideBlue. I think it's a Lazy loading problem that is related with this "limitation" from Hibernate (from https://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one?_sscc=t):

class B {  
    private C cee;  

    public C getCee() {  
        return cee;  
    }  

    public void setCee(C cee) {  
        this.cee = cee;  
    }  
}  

class C {  
    // Not important really  
}  

Right after loading B, you may call getCee() to obtain C. But look, getCee() is a method of YOUR class and Hibernate has no control over it. Hibernate does not know when someone is going to call getCee(). That means Hibernate must put an appropriate value into "cee" property at the moment it loads B from database.

If proxy is enabled for C, Hibernate can put a C-proxy object which is not loaded yet, but will be loaded when someone uses it. This gives lazy loading for one-to-one.

But now imagine your B object may or may not have associated C (constrained="false"). What should getCee() return when specific B does not have C? Null. But remember, Hibernate must set correct value of "cee" at the moment it set B (because it does no know when someone will call getCee()). Proxy does not help here because proxy itself in already non-null object.

So the resume: if your B->C mapping is mandatory (constrained=true), Hibernate will use proxy for C resulting in lazy initialization. But if you allow B without C, Hibernate just HAS TO check presence of C at the moment it loads B. But a SELECT to check presence is just inefficient because the same SELECT may not just check presence, but load entire object. So lazy loading goes away.

like image 129
Dherik Avatar answered Nov 15 '22 11:11

Dherik