Search code examples
javaspringhibernatespring-datahibernate-mapping

Hibernate @OneToOne loaded even though is lazy


I'm working with Spring Boot 2.3, Spring Data and Hibernate.

I've the following entities

@Entity
@Getter
@Setter
@EqualsAndHashCode(of = "id")
public class User {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long id;

    private String name;

    @OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
    private Address address;

    @Version
    private Long version;
}

@Entity
@Getter
@Setter
@EqualsAndHashCode(of = "id")
public class Address {

    @Id
    private Long id;

    private String fullAddress;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id")
    @MapsId
    private User user;

    @Version
    private Long version;    
}

When the following code is executed, any query related to the user repository is performed (and for me it is the expected behavior).

Address addressFromDb = addressRepository.findAll().get(0);
log.info("" + addressFromDb.getUser().getId());

// select address0_.id as id1_0_, address0_.full_address as full_add2_0_, address0_.version as version3_0_ from address address0_

but when I execute the following code, then there are multiple queries and I don't understanding why. Apparently the FetchType.LAZY from user to address is not honored.

User userFromDb = userRepository.findAll().get(0);

// select user0_.id as id1_4_, user0_.name as name2_4_, user0_.version as version3_4_ from user user0_
// select address0_.id as id1_0_0_, address0_.full_address as full_add2_0_0_, address0_.version as version3_0_0_ from address address0_ where address0_.id=?

What am I missing?

In order to be more helpful and more clear I've created the following github repo


Solution

  • Hibernate (or more specifically PersistenceContext) needs to know, whether the entity exists or not, so that it can decide, whether to provide a proxy for the entity or null. This does not apply for XToMany relationships, because the whole collection can be wrapped in a proxy and in special case it will be empty.

    It is also important to point out, that FetchType is just a suggestion for the JPa implementation and there is no guarantee, that in every case it will be fulfilled. You can read more about @OneToOne here, especially in terms of fetching strategy:

    While the unidirectional @OneToOne association can be fetched lazily, the parent-side of a bidirectional @OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad.

    Even if the FK is NOT NULL and the parent-side is aware about its non-nullability through the optional attribute (e.g. @OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)), Hibernate still generates a secondary select statement.

    For every managed entity, the Persistence Context requires both the entity type and the identifier, so the child identifier must be known when loading the parent entity, and the only way to find the associated post_details primary key is to execute a secondary query.

    Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with @LazyToOne(LazyToOneOption.NO_PROXY) and the child side is not using @MapsId.