Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projections do not load ManyToOne relations lazily #2183

Closed
s3curitybug opened this issue Mar 18, 2021 · 5 comments
Closed

Projections do not load ManyToOne relations lazily #2183

s3curitybug opened this issue Mar 18, 2021 · 5 comments
Assignees
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@s3curitybug
Copy link

s3curitybug commented Mar 18, 2021

Hello. I have an entity representing a MySQL table:

@Entity
@Table(name = "group_")
@Getter
@Setter
@SuppressWarnings("serial")
public class GroupEntity
        implements Serializable {

    @Id
    @GeneratedValue(generator = "uuid2")
    @Column(name = "group_id")
    private UUID groupId;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "owner_account_id")
    @NotNull
    private AccountEntity ownerAccount;

    @Column(name = "value")
    @NotBlank
    private String value;

}

And a projection:

public interface GroupBaseProjection {

    UUID getGroupId();

    AccountEntity getOwnerAccount();

}

This is my repository:

public interface GroupsRepository extends JpaRepository<GroupEntity, UUID> {

    GroupBaseProjection findBaseByGroupId(UUID groupId);

}

When I retrieve the entity, the ownerAccount is loaded lazily (the Hibernate MySQL query only selects its ID) but when I retrieve the projection, it is loaded eagerly (the Hibernate MySQL query does an inner join with all the AccountEntity columns). So there is no way I can retrieve only some columns of the GroupEntity (I have to retrieve all of them using the entity, or some of them plus all the columns of the AccountEntity using the projection).

Is there a way I can get the projection to load the @ManyToOne relation lazily? I've tried annotating the getOwnerAccount() method of projection with @ManyToOne(fetch = FetchType.LAZY, optional = false) and @LazyToOne(LazyToOneOption.PROXY) but it did not work.

EDIT:

Changing GroupBaseProjection so that getOwnerAccount() returns a nested projection instead of an entity should solve the problem, but it does not since nested projections select all columns, not just those defined in its getters (because this feature request was rejected: #1555)

Maybe using DTOs instead of projections could solve the problem, but they cannot be used either because of this bug: #2009

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 18, 2021
@schauder
Copy link
Contributor

The idea of projections is to have exactly those columns in the projection, that you need. If you need some data only sometimes the correct approach would be to have separate projections for the different cases.

@s3curitybug
Copy link
Author

s3curitybug commented Mar 19, 2021

That is theoretically all right. But in the real world I have an entity with 8 attributes (and 6 projections combining them), 3 of them are a ManyToOne relation to other entities, each with 8 attributes (and more than 5 projections each). Which means that I would need more than 625 projections just for that entity. Even if I implemented them, I would still not be able to perform optimized queries since nested projections select all the fields of its entity, not just those defined by its getters.

That is why I preffer to select just the ID of the ManyToOne relation and then use it to perform another query to retrieve an specific projection of the related entity. But I cannot achieve this either since the ManyToOne relation is always fetched eagerly.

In other words, in a projection with a ManyToOne relation, the related entity always gets all its columns selected (even if it is a nested projection and even if it is annotated to get lazily fetched). Which pretty much makes projections useless (except those that do not need any relation, which is not usual in the real world) since the only way not to select all the attributes of an entity implies selecting all the attributes of its ManyToOne related entities. The mechanism that JPA offers to load less columns from the database to optimize queries ends up loading more columns.

@schauder schauder removed the status: waiting-for-triage An issue we've not yet triaged label Mar 22, 2021
@schauder schauder self-assigned this Mar 22, 2021
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 22, 2021
@schauder schauder added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 22, 2021
@schauder
Copy link
Contributor

I can see your problem. This is not what Spring Data projections are intended for and we won't add lazy loading for them.

Use JPA/the EntityManager directly to construct the queries you need.

@s3curitybug
Copy link
Author

Could you at least consider implementing nested projections in a way that they only select the columns defined in their getters (as discussed in #1555)?

@tuan-tr
Copy link

tuan-tr commented Jun 13, 2021

LEFT JOIN FETCH works for me.
Try

@Query(value =
  "SELECT gr FROM GroupEntity gr " +
  "LEFT JOIN FETCH gr.ownerAccount " +
  "WHERE gr.groupId = :groupId ")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

4 participants