Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(1+N) selects with OnetoOne associations

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.

like image 763
David Avatar asked Aug 21 '10 12:08

David


People also ask

How to Map One-to-One relationship in JPA?

The One-To-One mapping represents a single-valued association where an instance of one entity is associated with an instance of another entity. In this type of association one instance of source entity can be mapped atmost one instance of target entity.

How to Map One-to-One relationship in hibernate?

Define Hibernate Mapping FileThe <many-to-one> element will be used to define the rule to establish a one-to-one relationship between EMPLOYEE and ADDRESS entities, but column attribute will be set to unique constraint and rest of the mapping file will remain as it was in case of many-to-one association.

What is difference between JPA unidirectional OneToOne and ManyToOne?

According to book Pro JPA 2 the main difference between unidirectional @ManyToOne and @OneToOne is that in @OneToOne: Only one instance of the source entity can refer to the same target entity instance. In other words, the target entity instance is not shared among the source entity instances.

How do I get a OneToOne map?

The best way to map a @OneToOne relationship is to use @MapsId . This way, you don't even need a bidirectional association since you can always fetch the PostDetails entity by using the Post entity identifier. This way, the id property serves as both Primary Key and Foreign Key.


Video Answer


1 Answers

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

  • OneToOne relationship with shared primary key generates n+1 selects; any workaround?
  • Making a OneToOne-relation lazy
  • fetchmode in jpa 2 criteriaquery

Reference

  • JPA 1.0 specification
    • Section 9.1.23 "OneToOne Annotation"
like image 102
Pascal Thivent Avatar answered Oct 11 '22 09:10

Pascal Thivent