73

I faced the problem that one-to-one lazy loading doesn't work in hibernate. I've already solved it, but still don't properly understand what happens.

My code (lazy loading doesn't work here, when I pull Person - Address is also fetched):

@Entity
public class Person{

  @Id
  @SequenceGenerator(name = "person_sequence", sequenceName = "sq_person")
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_sequence")
  @Column(name = "id")
  private long personID;

  @OneToOne(mappedBy="person", cascade=CascadeType.ALL, fetch = FetchType.LAZY)
  private Adress address;
  //.. getters, setters
}

@Entity
public class Address {

  @Id
  @Column(name="id", unique=true, nullable=false)
  @GeneratedValue(generator="gen")
  @GenericGenerator(name="gen", strategy="foreign", parameters=@Parameter(name="property", value="person"))
  private long personID;

  @PrimaryKeyJoinColumn
  @OneToOne
  private FileInfo person;
}

But: if I add optional=false in OneToOne relationship, lazy loading works fine!

@OneToOne(mappedBy="person", cascade=CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
private Adress address;

Question/Entreaty: please, explain to me how optional=false annotation helps to achieve lazy loading.

P.S. I've read posts post1 and post2, and understand why simple OneToOne can't be lazy, but I still can't grasp optional=false magic.

Martijn Pieters
  • 889,049
  • 245
  • 3,507
  • 2,997
VB_
  • 43,322
  • 32
  • 111
  • 238
  • Hey @Volodymyr, I have the same problem with you. I'm trying to separate a BLOB column from an entity. Parent entity has child entity. Child entity contains binary column. Parent and child are the `same table` so I use @OneToOne relationship. Although I used LAZY fetchType but it seems doesn't work. When I put `optional=false`, it works. Any explanation will be appreciated really. – emeraldhieu Oct 21 '15 at 08:35
  • @Emerald214 sorry, that was 2 years ago. Currently I'm writting JS Mobile, and can't help you – VB_ Oct 21 '15 at 08:38
  • OneToOne optional = false doesn't work with CascadeType.PERSIST see: https://hibernate.atlassian.net/browse/HHH-9670 – sliver Jul 05 '17 at 06:51

2 Answers2

99

If the association is optional, Hibernate has no way of knowing if an address exists for a given person without issuing a query. So it can't populate the address field with a proxy, because there could be no address referencing the person, and it can't populate it with null, because there might be an address referencing the person.

When you make the association mandatory (i.e. optional=false), it trusts you and assumes that an address exists, since the association is mandatory. So it directly populates the address field with a proxy, knowing that there is an address referencing the person.

Kihats
  • 2,110
  • 2
  • 19
  • 34
JB Nizet
  • 633,450
  • 80
  • 1,108
  • 1,174
  • 1
    optional=false is not working if you try to save the Personne without Adresse : "org.hibernate.PropertyValueException: not-null property references a null or transient value: " – Grégory Nov 28 '13 at 18:30
  • 6
    optional = false means that... the address is not optional. So it's mandatory. So setting it to null throws an exception. That's quite expected. – JB Nizet Nov 28 '13 at 18:34
  • So, OneToOne, lazy loading, with shared Primary key and optional on one side, is just impossible with hibernate, right ? – Grégory Nov 29 '13 at 09:19
  • 3
    It's possible except that it won't actually lazy-load the association. It's also possible by using LazyToOne(NO_PROXY) and instrumenting the byte-code at build time, IIRC, but I've had bad experiences with that. You'd better use a dedicated join column if the association is optional. – JB Nizet Nov 29 '13 at 09:56
  • 1
    Yes, I've tested LazyToOne(NO_PROXY) and instrumenting the byte code at build-time but have bad experience like you ( With another lib HibernateJackson ). I've finally decide to not map the association and managed the table separately in differents DAO. – Grégory Nov 29 '13 at 16:26
  • 1
    optional = false, does not work for me, it still fetches those entity eagerly. @OneToOne(fetch = FetchType.LAZY, mappedBy = "fundSeries", optional = false) private FundSeriesDetailEntity fundSeriesDetail; – Oleg Kuts Oct 06 '16 at 14:02
  • @JB Nizet , You should modify your answer.. Hibernate version's over 4.3.7 , optional=false , for onetoone relations , lazy loading is not working. Check https://hibernate.atlassian.net/browse/HHH-9515 – Gursel Koca Apr 12 '18 at 15:47
  • So if `optional = true` it's Schrödinger's address. Hibernate doesn't know if there is an address or not until it fetches it. – egelev Sep 26 '20 at 13:49
11

The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.

The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add @LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.

The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case @LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.

sendon1982
  • 7,088
  • 42
  • 36