14

Considering the following model:

@Entity
public class User {

    @Id
    @Column(name = "USER_ID")
    private Long userId;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "LAST_NAME")
    private String lastName;

    @OneToOne
    @PrimaryKeyJoinColumn
    private UserExt userExt;
...     //getters and setters

}

@Entity
public class UserExt {

    @Id
    @Column(name="USER_ID")
    private Long id;

    private String cdpId;

    private Date lastChanged;
...     //getters and setters
}

when executing :

Query query = session.createQuery("from User");
List<User> list = query.list();

Hibernate executes

Hibernate: select user0_.USER_ID as USER1_0_, user0_.FIRST_NAME as FIRST2_0_, user0_.LAST_NAME as LAST3_0_, user0_.EXT_USERNAME as EXT4_0_ from USER user0_
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
Hibernate: select userext0_.USER_ID as USER1_1_0_, userext0_.cdpId as cdpId1_0_, userext0_.lastChanged as lastChan3_1_0_ from USER_EXT userext0_ where userext0_.USER_ID=?
...
...

Using a query with specific properties works (select u.firstName, u.userExt.cdpId).

However since I want the full User Entity ("from User"), hibernate generates one select for each result row in the first.

I don't get it since the default fetch strategy should be LAZY not EAGER. Forcing it to LAZY didn't fix the problem.

Pascal Thivent
  • 535,937
  • 127
  • 1,027
  • 1,106
David
  • 850
  • 1
  • 12
  • 22

3 Answers3

12

There are two problems preventing lazy loading here:

  1. The default fetch strategy of OneToOne is EAGER (and keep in mind that LAZY is just a hint to the persistence provider).
  2. LAZY can only work on a OneToOne association if the association is non-nullable (at least without using bytecode instrumentation).

9.1.23 OneToOne Annotation

The OneToOne annotation defines a single-valued association to another entity that has one-to-one multiplicity. It is not normally necessary to specify the associated target entity explicitly since it can usually be inferred from the type of the object being referenced.

Table 16 lists the annotation elements that may be specified for a OneToOne annotation and their default values.

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToOne {
    Class targetEntity() default void.class;
    CascadeType[] cascade() default {};
    FetchType fetch() default EAGER;
    boolean optional() default true;
    String mappedBy() default "";
}

I tested the following:

@OneToOne(optional = false, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private UserExt userExt;

And confirm that a simple from User only loads all the users

Hibernate: 
    select 
        user0_.USER_ID as USER1_0_,
        user0_.FIRST_NAME as FIRST2_0_,
        user0_.LAST_NAME as LAST3_0_
    from 
        USER user0_

And doesn't perform N additional queries, the UserExt are loaded lazily.

So, if you association is mandatory, use the appropriate mapping :) And if it is non-mandatory, you'll have to either:

  • use bytecode instrumentation and no-proxy fetch (see related question below)
  • use a fake ManyToOne instead (didn't test this mapping with a shared primary key)
  • eager load the UserExt using a join fetch to avoid the N subsequent selects (of course, this somehow defeats the point of a separate table)

Note that Hibernate >= 3.x ignores the Fetch annotation when you use the Query interface. In that case, you need to write that explicitly. this is an example:

EntityManager em = [...]
[...]
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);

Root<User> usersRoot = criteria.from(User.class);
usersRoot.fetch("Address", JoinType.LEFT);

List<User> users = em.createQuery(criteria).getResultList();

Related questions

Reference

  • JPA 1.0 specification
    • Section 9.1.23 "OneToOne Annotation"
Pascal Thivent
  • 535,937
  • 127
  • 1,027
  • 1,106
3

Default fetching strategy when using -ToOne such as @ManyToOne and @OneToOne is fetch=FetchType.EAGER NOT fetch=FetchType.LAZY

But Hibernate HQL overrides default fetching strategy. If you want to retrieve a fully initialized object by using just one query you must call

from 
    User u
 left join fetch 
    u.userExt
Arthur Ronald
  • 31,649
  • 18
  • 106
  • 135
0

use optional =true with a one-to-one relationship like this to avoid the n+1 issue with your own Fetch Strategy.

@OneToOne(fetch = FetchType.LAZY, optional=true)
@PrimaryKeyJoinColumn
Shaam
  • 113
  • 10