spring-data-jpa 我不知道为什么使用QueryDSL会出现N+1问题

enyaitl3  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(245)

我使用了Sping Boot 和QueryDSL。
当在ClientMemoRepositoryImpl.java中调用findAllByWriterGroupByClient方法时,[生成的查询1]生成一次,[生成的查询2]生成多次。
此外,当读取此查询的结果作为ClientMemoServiceImpl.java中的元组时,会多次生成[生成的查询3]。

  • ClientMemoRepositoryImpl.java
@Override
public List<Tuple> findAllByWriterGroupByClient(String searchKeyword, Long writerId, boolean hasAdminRole) {
    QClientMemo qClientMemo1 = new QClientMemo("cm1");
    QClientMemo qClientMemo2 = new QClientMemo("cm2");

    JPAQuery<Tuple> memoDtoJPAQuery = qf.select(
                    JPAExpressions.selectFrom(qClientMemo1)
                            .where(qClientMemo1.clientId.eq(qClientMemo.clientId).and(
                                            qClientMemo1.createdDate.eq(
                                                    JPAExpressions
                                                            .select(qClientMemo2.createdDate.max())
                                                            .from(qClientMemo2)
                                                            .where(qClientMemo2.clientId.eq(qClientMemo.clientId))
                                            )
                                    )
                            ),
                    new CaseBuilder()
                            .when(qClientMemo.createdDate.gt(LocalDateTime.now().minusDays(7)))
                            .then(1)
                            .otherwise(0).sum()

            )
            .from(qClientMemo);

    if ((!hasAdminRole) && writerId != null) {
        memoDtoJPAQuery = memoDtoJPAQuery.where(qClientMemo.writer.id.eq(writerId));
    }

    if (searchKeyword != null)
        memoDtoJPAQuery = memoDtoJPAQuery.where(
                qClientMemo.title.contains(searchKeyword)
                        .or(qClientMemo.content.contains(searchKeyword))
                        .or(qClientMemo.clientId.clientName.contains(searchKeyword))
                        .or(qClientMemo.writer.name.contains(searchKeyword))
        );

    return memoDtoJPAQuery
            .groupBy(qClientMemo.clientId)
            .orderBy(OrderByNull.DEFAULT)
            .fetch();
}
  • 生成的查询1
select
    (select
        clientmemo1_.id 
    from
        client_memo clientmemo1_ 
    where
        clientmemo1_.client_id=clientmemo0_.client_id 
        and clientmemo1_.created_date=(
            select
                max(clientmemo2_.created_date) 
            from
                client_memo clientmemo2_ 
            where
                clientmemo2_.client_id=clientmemo0_.client_id
        )
    ) as col_0_0_, sum(case 
        when clientmemo0_.created_date>? then ? 
        else 0 
    end) as col_1_0_ 
from
    client_memo clientmemo0_ 
group by
    clientmemo0_.client_id 
order by
    null asc
  • 生成的查询2
select
    [all fields of client_memo entity] 
from
    client_memo clientmemo0_ 
where
    clientmemo0_.id=?
  • 生成的查询3
select
    [all fields of client entity]
from
    client client0_ 
where
    client0_.id=?
  • ClientMemoServiceImpl.java
List<Tuple> clientMemos = clientMemoRepository.findAllByWriterGroupByClient(
                    readClientMemoDto.getSearchKeyword(),
                    readClientMemoDto.getUserId(),
                    hasAdminRole
            );

clientMemos.forEach(clientMemo -> {
    Map<String, Object> result = new HashMap<>();

    Integer newCnt = clientMemo.get(1, Integer.class);
    if (newCnt != null) {
        result.put("newMemoNum", newCnt);
    }

    MemoDto memoDto = new MemoDto();
    ClientMemo memo = clientMemo.get(0, ClientMemo.class);
    if (memo != null) {
        memoDto.ofClientMemo(memo);
        result.put("memoDetail", memoDto);
    }

    results.add(result);
});
  • ClientMemo.java
@Entity
@Table(name = "client_memo")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@DynamicInsert
public class ClientMemo {

    @JsonIgnore
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Lob
    @Column(name = "content")
    private String content;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="client_id")
    private Client clientId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="writer")
    private User writer;

    @Column(name = "created_date")
    private LocalDateTime createdDate;

    @Column(name = "updated_date")
    private LocalDateTime updatedDate;

    @Column(name = "is_admin")
    private boolean isAdmin;

}
  • Client.java
@Entity
@Table(name = "client")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@DynamicInsert
public class Client {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "client_name", nullable = false)
    private String clientName;

    @Column(name = "client_phone_num", nullable = false)
    private String clientPhoneNum;

    @Column(name = "service_start_time")
    private LocalDateTime serviceStartTime;

    @Column(name = "service_end_time")
    private LocalDateTime serviceEndTime;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "media_id")
    private Media media;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "person_charge_id")
    private User personCharge;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "normal_memo")
    private ClientMemo normalMemo;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "admin_memo")
    private ClientMemo adminMemo;

    @Column(name = "status", columnDefinition = "varchar(20) default 'UNCONTRACTED'")
    @Enumerated(EnumType.STRING)
    private ClientStatus status;

    @Column(name = "is_deleted", nullable = false)
    private boolean isDeleted;

}

数据关系的所有FetchType都是FetchType.LAZY
我不明白为什么会出现这个问题,为什么有些人说使用FetchType.LAZY比使用FetchType.EAGER更好。
我对QueryDSL或SpringBoot的理解是否正确?
谢谢

k5hmc34c

k5hmc34c1#

您应该与我们分享您的Jpa实体。
在我看来,你应该在你的实体中设置一些关联(@OneToMany等),可能是在Eager模式(默认模式)下。
当您尝试从数据库加载对象的一个示例时,Hibernate也会加载关联。在急切模式下,Hibernate通过查询数据库(这会生成额外的sql查询)来加载关联。
如果您在Lazy模式下定义关联,Hibernate将使用一些代理对象填充实体jpa,并在稍后获取关联,但仅在您访问关联时获取(这意味着,当您仅尝试在代码中访问关联时,关联的sql查询将被延迟)。

相关问题