【问题标题】:Why is JPA query so slow?为什么 JPA 查询这么慢?
【发布时间】:2017-01-21 08:19:25
【问题描述】:

我正在使用 JPA 存储库在我的 Web 应用程序中实现查询。我查询的两个主要表是FmReportTbSpecimenTb

这里是两个实体类(只列出了重要的属性)。

//FmReportTb.java
@Entity
@Table(name="FM_REPORT_TB")
public class FmReportTb implements Serializable {

    @Column(name="ROW_ID")
    private long rowId;

    @Column(name="FR_BLOCK_ID")
    private String frBlockId;

    @Column(name="FR_FULL_NAME")
    private String frFullName;
    @OneToOne
    @JoinColumn(name="SPECIMEN_ID")
    private SpecimenTb specimenTb;

FmReportTbSpecimenTb 具有 OneToOne 关系。

@Entity
@Table(name="SPECIMEN_TB")
public class SpecimenTb implements Serializable {
    private String mrn;
    @OneToOne(mappedBy="specimenTb", cascade=CascadeType.ALL)
    private FmReportTb fmReportTb;

我正在处理的查询是查找FmReportTb 中的所有记录,并显示来自FmReportTb 的一些属性以及来自SpecimenTbmrn。 这是我的 FmReportTb 的 JPA 存储库:

@Repository
public interface FmReportRepository extends JpaRepository<FmReportTb, Long> {
    @Query("select f from FmReportTb f where f.deleteTs is not null")
    public List<FmReportTb> findAllFmReports();

因为,我只显示来自FmReportTb 的部分属性和来自SpecimenTb 的一个属性,我决定为FmReportTb 创建一个值对象。 VO 类的构造函数从FmReportTb 中分配属性,并根据OneToOne 关系从SpecimenTb 中获取mrn 属性。使用 VO 的另一个原因是表 FmReportTb 有很多 OneToMany 子实体。对于这个特定的查询,我不需要它们中的任何一个。

public class FmReportVO {
    private String frBlockId;
    private Date frCollectionDate;
    private String frCopiedPhysician;
    private String frDiagnosis;
    private String frFacilityName;
    private String frFullName;
    private String frReportId;
    private String filepath;
    private String mrn;

    public FmReportVO(FmReportTb fmReport) {
        this.frBlockId = fmReport.getFrBlockId();
        this.frCollectionDate = fmReport.getFrCollectionDate();
        this.frCopiedPhysician = fmReport.getFrCopiedPhysician();
        this.frDiagnosis = fmReport.getFrDiagnosis();
        this.frFacilityName = fmReport.getFrFacilityName();
        this.frFullName = fmReport.getFrFullName();
        this.frReportId = fmReport.getFrReportId();
        this.mrn = fmReport.getSpecimenTb().getMrn();
    }

我在 servicebean 类中实现了 findall 方法以返回 FmReportTb VO 列表。

//FmReportServiceBean.java
    @Override
    public List<FmReportVO> findAllFmReports() {
        List<FmReportTb> reports = fmReportRepository.findAllFmReports();
        if (reports == null) {
            return null;
        }
        List<FmReportVO> fmReports = new ArrayList<FmReportVO>();
        for (FmReportTb report : reports) {
            FmReportVO reportVo = new FmReportVO(report);
            String filepath = fileLoadRepository.findUriByFileLoadId(report.getFileLoadId().longValue());
            reportVo.setFilepath(filepath);
            fmReports.add(reportVo);
        }
        return fmReports;
    }

最后,我的控制器是这样的:

@RequestMapping(
        value = "/ristore/foundation/",
        method = RequestMethod.GET,
        produces = "application/json")
public ResponseEntity<List<FmReportVO>> getAllFmReports() {
    List<FmReportVO> reports = ristoreService.findAllFmReports();
    if (reports == null) {
        return new ResponseEntity<List<FmReportVO>>(HttpStatus.NOT_FOUND);
    }
    return new ResponseEntity<List<FmReportVO>>(reports, HttpStatus.OK);
}

数据库中有大约 200 条记录。令人惊讶的是,检索 JSON 格式的所有记录花了将近 2 秒的时间。即使我没有索引所有表,这也太慢了。类似的查询直接在数据库上大约需要几毫秒。是因为我使用的是值对象还是 JPA 查询往往这么慢?

编辑 1 这可能与 FmReportTb 有近 20 个 OneToMany 实体的事实有关。尽管这些子实体的 fetchmode 设置为 LAZY,JPA Data repository tends to ignore the fetchmode。所以我最终使用NamedEntityGraph 来指定属性EAGER。下一部分将添加到我的 FmReportTb 实体类的头部。

@Entity
@NamedEntityGraph(
        name = "FmReportGraph", 
        attributeNodes = { 
          @NamedAttributeNode("fileLoadId"),
          @NamedAttributeNode("frBlockId"),
          @NamedAttributeNode("frCollectionDate"),
          @NamedAttributeNode("frDiagnosis"),
          @NamedAttributeNode("frFullName"),
          @NamedAttributeNode("frReportId"),
          @NamedAttributeNode("specimenTb")})
@Table(name="FM_REPORT_TB")

然后@EntityGraph("FmReportGraph") 被添加到 JPA 存储库查询之前以查找所有记录。这样做之后,性能会有所提高。现在获取 1500 条记录只需要大约 10 秒。但是,鉴于每个 json 对象都相当小,它似乎仍然太慢。

【问题讨论】:

  • 在进行直接查询时,您似乎没有使用任何 join 子句。这就是直接查询工作如此之快的原因。但是hibernate会使用join。如果外键的列没有被索引,使用join 查询会很慢。另一个怀疑是您的findUriByFileLoadId 方法。可能存在延迟。
  • @KenBekov 这可能是由于 JPA 数据存储库对许多单一实体进行了 EAGER 处理(请参阅我对原始帖子的编辑)。通过使用 EntityGraph 选择 EAGER 属性,查询时间减少了,但仍然很慢。有没有办法让应用程序打印出已经执行的实际休眠 SQL?或者更好地记录每个人花费的时间。
  • Hibernate 有&lt;property name="show_sql"&gt;true&lt;/property&gt;。您可以添加它的休眠配置。您是否为外键的引用列添加了索引?

标签: spring hibernate rest jpa-2.0


【解决方案1】:

通过慢速 JPA 查询为他人的利益作出回答...

正如@Ken Bekov 在 cmets 中所暗示的,外键对 JPA 有很大帮助。

我有几个具有多对一关系的表 - 对 100,000 条记录的查询需要数小时才能执行。在没有任何代码更改的情况下,我只需添加一个外键就可以将其缩短到几秒钟。

在 phpMyAdmin 中,您通过创建从“多”表到“一”表的关系来完成此操作。详细解释见这个问题:Setting up foreign keys in phpMyAdmin? 以及@Devsi Odedra 的回答

【讨论】:

    猜你喜欢
    • 2011-03-11
    • 2020-03-19
    • 2014-03-12
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多