简体   繁体   中英

Hibernate OneToMany collection is loaded even if lazy

I have an entity User , with a property wishes that should be lazy loaded as stated in the FetchType attribute of the @OneToMany annotation:

@Entity
@Table(name = "user")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@DynamicUpdate
public class User extends AbstractEntity<Long> {

    @Column(name = "user_email", unique = true)
    private String email;

    @JsonManagedReference
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
    private Set<Wish> wishes = new HashSet<>();

    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof User)) return false;

        final User user = (User) other;

        if (!user.getEmail().equals(getEmail())) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getEmail().hashCode();
        result = 29 * result;
        return result;
    }

The entity Wish follows:

@Entity
@Table(name = "wish",
    uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "product_id"}))
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@AllArgsConstructor
@Immutable
public class Wish extends AbstractPersistable<Long> {

    @JsonBackReference
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof User)) return false;

        final Wish wish = (Wish) other;

        if (!wish.getId().equals(getId())) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getId().hashCode();
        result = 29 * result;
        return result;
    }
}

The problem is that when I run the following code in a service class:

    @Transactional(readOnly = true)
    public User getUserWithRolesOnly(String username) {
        Optional<User> optionalUser = userRepository.findOneWithRoles(username);
        ....
        // HERE, user.wishes is loaded up even if it is LAZY
        ...
        return user;
    }

Repository code:

    @Query("SELECT u FROM User u " +
        "LEFT JOIN FETCH u.roles " +
        "WHERE u.email = ?1")
    Optional<User> findOneWithRoles(String email);

The user object is loaded along with the wishes. How come? I would expect it not to be loaded if not requested (since it's LAZY fetched).

Why is wishes loaded instead?

I see it being loaded also by debugging the hibernate code. Logs:

2021-10-02 12:08:38.501 TRACE 20808 --- [nio-8080-exec-2] org.hibernate.type.CollectionType        : Created collection wrapper: [com.test.impl.User.wishes#3]

The collection is loaded because some code, probably Jackson during serialization, is accessing the data through eg reflection. Try adding @JsonIgnore to your wishes association to observe that Jackson is the problem here.

To solve this properly, you need DTOs that prevent access to information that you don't want to expose and I think this is a perfect use case forBlaze-Persistence Entity Views .

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(User.class)
public interface UserDto {
    @IdMapping
    Long getId();
    String getEmail();
    Set<RoleDto> getRoles();

    @EntityView(Role.class)
    interface RoleDto {
        @IdMapping
        Long getId();
        String getName();
    }
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

UserDto a = entityViewManager.find(entityManager, UserDto.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<UserDto> findAll(Pageable pageable);

The best part is, it will only fetch the state that is actually necessary!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM