How can I make a JPA OneToOne relation lazy
Unless you are using Bytecode Enhancement, you cannot fetch lazily the parent-side @OneToOne
association.
However, most often, you don't even need the parent-side association if you use @MapsId
on the client side:
@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {
@Id
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
private Post post;
public PostDetails() {}
public PostDetails(String createdBy) {
createdOn = new Date();
this.createdBy = createdBy;
}
//Getters and setters omitted for brevity
}
With @MapsId
, the id
property in the child table serves as both Primary Key and Foreign Key to the parent table Primary Key.
So, if you have a reference to the parent Post
entity, you can easily fetch the child entity using the parent entity identifier:
PostDetails details = entityManager.find(
PostDetails.class,
post.getId()
);
This way, you won't have N+1 query issues that could be caused by the mappedBy
@OneToOne
association on the parent side.
To get lazy loading working on nullable one-to-one mappings you need to let hibernate do compile time instrumentation and add a @LazyToOne(value = LazyToOneOption.NO_PROXY)
to the one-to-one relation.
Example Mapping:
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()
Example Ant Build file extension (for doing the Hibernate compile time instrumentation):
<property name="src" value="/your/src/directory"/><!-- path of the source files -->
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries -->
<property name="destination" value="/your/build/directory"/><!-- path of your build directory -->
<fileset id="applibs" dir="${libs}">
<include name="hibernate3.jar" />
<!-- include any other libraries you'll need here -->
</fileset>
<target name="compile">
<javac srcdir="${src}" destdir="${destination}" debug="yes">
<classpath>
<fileset refid="applibs"/>
</classpath>
</javac>
</target>
<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
<classpath>
<fileset refid="applibs"/>
</classpath>
</taskdef>
<instrument verbose="true">
<fileset dir="${destination}">
<!-- substitute the package where you keep your domain objs -->
<include name="/com/mycompany/domainobjects/*.class"/>
</fileset>
</instrument>
</target>
First off, some clarifications to KLE's answer:
Unconstrained (nullable) one-to-one association is the only one that can not be proxied without bytecode instrumentation. The reason for this is that owner entity MUST know whether association property should contain a proxy object or NULL and it can't determine that by looking at its base table's columns due to one-to-one normally being mapped via shared PK, so it has to be eagerly fetched anyway making proxy pointless. Here's a more detailed explanation.
many-to-one associations (and one-to-many, obviously) do not suffer from this issue. Owner entity can easily check its own FK (and in case of one-to-many, empty collection proxy is created initially and populated on demand), so the association can be lazy.
Replacing one-to-one with one-to-many is pretty much never a good idea. You can replace it with unique many-to-one but there are other (possibly better) options.
Rob H. has a valid point, however you may not be able to implement it depending on your model (e.g. if your one-to-one association is nullable).
Now, as far as original question goes:
A) @ManyToOne(fetch=FetchType.LAZY)
should work just fine. Are you sure it's not being overwritten in the query itself? It's possible to specify join fetch
in HQL and / or explicitly set fetch mode via Criteria API which would take precedence over class annotation. If that's not the case and you're still having problems, please post your classes, query and resulting SQL for more to-the-point conversation.
B) @OneToOne
is trickier. If it's definitely not nullable, go with Rob H.'s suggestion and specify it as such:
@OneToOne(optional = false, fetch = FetchType.LAZY)
Otherwise, if you can change your database (add a foreign key column to owner table), do so and map it as "joined":
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()
and in OtherEntity:
@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()
If you can't do that (and can't live with eager fetching) bytecode instrumentation is your only option. I have to agree with CPerkins, however - if you have 80!!! joins due to eager OneToOne associations, you've got bigger problems then this :-)
The basic idea behing the XToOnes in Hibernate is that they are not lazy in most case.
One reason is that, when Hibernate have to decide to put a proxy (with the id) or a null,
it has to look into the other table anyway to join. The cost of accessing the other table in the database is significant, so it might as well fetch the data for that table at that moment (non-lazy behaviour), instead of fetching that in a later request that would require a second access to the same table.
Edited: for details, please refer to ChssPly76 's answer. This one is less accurate and detailed, it has nothing to offer. Thanks ChssPly76.