Java – JPA/Hibernate: @ManyToOne and @OneToOne relationships tagged as FetchType.LAZY and optional = false not loading lazily on em.find()

hibernatejavajpalazy-loadingrelationships

I have the following entity (only relevant mappings shown):

@Entity
@Table(name = "PQs")
public class PQ implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Integer id;

    @Column
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)                 // lazy XToOne
    @JoinColumn(name = "user_id", referencedColumnName = "person_id")
    private User user;

    @OneToOne(mappedBy = "pq", fetch = FetchType.LAZY) // lazy XToOne
    private Group group;

    @OneToOne(mappedBy = "pq", fetch = FetchType.LAZY) // lazy XToOne
    private Tendering tendering;

    ...
}

Note the comments above: there are three @XToOne relationships to other entities:

User (a SecurityIdentity sub class with a simple ID as PK, referenced by PQ representing the owner):

@Entity
@Table(name = "Users")
@DiscriminatorValue(value = "user")
public class User extends SecurityIdentity
{
    @Column
    private String name;

    @OneToMany(mappedBy = "user")
    private Set<PQ> pqs = new HashSet<PQ>();

    ...
}

Group (also a SecurityIdentity sub class with a simple ID as PK, references the PQ to represent a set of users that can interact with that PQ):

@Entity
@Table(name = "Groups")
@DiscriminatorValue(value = "group")
public class Group extends SecurityIdentity
{
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pq_id", referencedColumnName = "id")
    private PQ pq;

    ...
}

Tendering:

@Entity
@Table(name = "Tenderings")
public class Tendering implements Serializable
{
    @Id
    @Column(name = "pq_id", insertable = false, updatable = false)
    private Integer pqId;

    @Column(name = "external_code")
    private String externalCode;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pq_id", referencedColumnName = "id")
    private PQ pq;

    ...
}

Don't be confused about groups and users sharing IDs, just treat them as simple IDs. A tendering is just a separate document object (one-to-one).

As you can see there are three @XToOne relationships on the PQ entity, which, if no fetch type was set, would be loaded eagerly (JPA default). So to prevent this I tagged all @XToOne relationships as FetchType.LAZY.

Now when using

em.find(PQ.class, someExistingId);

I get the Hibernate output:

23:53:55,815 INFO  [stdout] Hibernate: select pq0_.id as id291_0_, pq0_.description as descript2_291_0_, pq0_.name as name291_0_, pq0_.submission_date as submission4_291_0_, pq0_.user_id as user5_291_0_ from PQs pq0_ where pq0_.id=?
23:53:55,818 INFO  [stdout] Hibernate: select user0_.id as id280_0_, user0_1_.identity_type_id as identity2_280_0_, user0_.is_enabled as is1_297_0_, user0_.name as name297_0_, user0_.password as password297_0_, user0_.person_id as person5_297_0_ from Users user0_ inner join SecurityIdentities user0_1_ on user0_.id=user0_1_.id where user0_.person_id=?
23:53:55,821 INFO  [stdout] Hibernate: select group0_.id as id280_0_, group0_1_.identity_type_id as identity2_280_0_, group0_.pq_id as pq2_281_0_ from Groups group0_ inner join SecurityIdentities group0_1_ on group0_.id=group0_1_.id where group0_.pq_id=?
23:53:55,823 INFO  [stdout] Hibernate: select tendering0_.pq_id as pq1_296_0_, tendering0_.binary_file as binary2_296_0_, tendering0_.customer_id as customer6_296_0_, tendering0_.description as descript3_296_0_, tendering0_.external_code as external4_296_0_, tendering0_.title as title296_0_ from Tenderings tendering0_ where tendering0_.pq_id=?

The three extra SELECTs stem from the @XToOne relationships (as described in many places on the net). The source I was looking at mostly is this:

Making a OneToOne-relation lazy

As mentioned there, the @ManyToOne relationship User user shouldn't be fetched:

@ManyToOne(fetch=FetchType.LAZY) should work just fine.

… here the relationship from PQ to User, but it is fetched as you can see from the select user0_.id as id280_0_, ... statement…

For the other two Group group and Tendering tendering, both @OneToOne reverse mappings, the foreign keys reference the PQs table's PK (ID), resulting in the same mapping in the PQ entity.

Note that all three relationships aren't optional: a PQ always has an owner (user), and a PQ is always referenced by a tendering and a group entity. I just hadn't modeled that in JPA above yet…

So, when adding optional = false to the three relationships of the PQ entity:

@Entity
@Table(name = "PQs")
public class PQ implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Integer id;

    @Column
    private String name;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "user_id", referencedColumnName = "person_id")
    private User user;

    @OneToOne(mappedBy = "pq", fetch = FetchType.LAZY, optional = false)
    private Group group;

    @OneToOne(mappedBy = "pq", fetch = FetchType.LAZY, optional = false)
    private Tendering tendering;

    ...
}

… I get the following Hibernate output:

00:47:34,397 INFO  [stdout] Hibernate: select pq0_.id as id361_0_, pq0_.description as descript2_361_0_, pq0_.name as name361_0_, pq0_.submission_date as submission4_361_0_, pq0_.user_id as user5_361_0_ from PQs pq0_ where pq0_.id=?
00:47:34,410 INFO  [stdout] Hibernate: select user0_.id as id350_0_, user0_1_.identity_type_id as identity2_350_0_, user0_.is_enabled as is1_367_0_, user0_.name as name367_0_, user0_.password as password367_0_, user0_.person_id as person5_367_0_ from Users user0_ inner join SecurityIdentities user0_1_ on user0_.id=user0_1_.id where user0_.person_id=?
00:47:34,413 INFO  [stdout] Hibernate: select group0_.id as id350_0_, group0_1_.identity_type_id as identity2_350_0_, group0_.pq_id as pq2_351_0_ from Groups group0_ inner join SecurityIdentities group0_1_ on group0_.id=group0_1_.id where group0_.pq_id=?

Note, that I've only been playing with the optional = false on the PQ entity, as this is the one I use in em.find(...). (If this is not sufficient, please enlighten me.)

My question now is two-fold:

  1. Why is the @ManyToOne to the User entity fetched eagerly (given that is was said to be working lazily, see Making a OneToOne-relation lazy)?
  2. Why is only the OneToOne relationship to the Tendering entity left off being fetched? Is it because the Tendering entity references the PQ's PK column as a PK itself (@Id in Tendering), which the Group entity doesn't (regular relationship to the PQ's PK)?

What's wrong? How do I make these non-optional relationships lazy? (without code-instrumentation or other hacks, just plain annotations…)

I know the LAZY thing is just a hint for the JPA provider to do something about lazy loading or not, but in this case it seems as if something else is wrong (as part of it is working).

PS: I'm using Hibernate 4.0 BETA, the version that comes with JBoss 7.0.0.Final along with JPA annotations only (the above are all JPA 1.0 compatible).

Best Answer

there are differences between using hibernate annotations and jpa annotations and as far as i know, hibernate is lazy loaded by default except in some cases. here's a quick discussion:

http://community.jboss.org/wiki/AShortPrimerOnFetchingStrategies

Related Topic