Issue
Having entities structured in a tree with 3 levels I would like to fetch a list of projections of top level entity that include some properties of mid level entities, but skip fetching bottom level entities. I have a setup like one below:
@Entity
class TopLevelEntity {
@EmbeddedId
private TopLevelEntityId id;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "mid_level_entity_id")
private MidLevelEntity midLevelEntity;
@Column(name = "some_number")
private Integer someNumber;
}
@Entity
class MidLevelEntity {
@EmbeddedId
private MidLevelEntityId id;
@OneToOne(mappedBy = "midLevelEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private BottomLevelEntityId bottomLevelEntityId;
}
@Entity
class BottomLevelEntity {
@EmbeddedId
private BottomLevelEntityId id;
@OneToOne
@JoinColumn(name = "mid_level_entity_id")
private MidLevelEntityId midLevelEntityId;
}
interface TopLevelEntityProjection {
TopLevelEntityId getTopLevelEntityId();
@Value("#{target.midLevelEntity?.id}")
MidLevelEntityId getMidLevelEntityId();
}
@Repository
public interface TopLevelEntityRepository extends JpaRepository<TopLevelEntity, TopLevelEntityId> {
@EntityGraph(attributePaths = {"midLevelEntity.id"}, type = EntityGraph.EntityGraphType.FETCH)
@Query("select e from TopLevelEntity e where e.someNumber > :someNumber")
Page<TopLevelEntityProjection> findTopLevelEntitiesWithSomeNumberGreaterThanSomeOtherNumber(Integer someNumber, Pageable pageable);
}
First query that is being executed is fine -- it fetches top and mid level entities. However there is also a query for each of the bottom level entities -- they are lazily loaded even though they are not really used for anything. We could of course eagerly load bottom level entities to avoid multiple queries, but is there a way to skip those altogether?
Edit:
I can now see that this question can be simplified: same thing happens if I query MidLevelEntities directly. Despite projection not using BottomLevelEntities they are being lazily loaded anyway. At first I though that maybe that are accessed during some equals/hashCode call, but when I overriden the methods and setup a breakpoints in them, they were not called.
Solution
Hibernate ORM always fetch @OneToOne
associations because it needs to know if the association is null or it needs to return an object (the proxy when the association is lazy). Using FetchType.LAZY
doesn't change this behaviour.
I think there are three possible approaches to this:
If you know that the association always exists, you can use
@JoinColumn(nullable=false)
. This way Hibernate ORM doesn't have to check and it will fetch the entity lazilyUse a unidirectional one-to-one with
@MapsId
:@Entity class TopLevelEntity { @EmbeddedId private TopLevelEntityId id; @Column(name = "some_number") private Integer someNumber; } @Entity class MidLevelEntity { @EmbeddedId private MidLevelEntityId id; @OneToOne(fetch = FetchType.LAZY) @MapsId @JoinColumn(name = "top_level_entity_id") private TopLevelEntity topLevelEntity; }
You can enable bytecode enhancements and use the annotation
@LazyToOne
You can find more info for these use cases in the Hibernate ORM documentation: Bidirectional one-to-one lazy association
Answered By - Davide D'Alto
Answer Checked By - Timothy Miller (JavaFixing Admin)