【发布时间】:2017-01-21 08:19:25
【问题描述】:
我正在使用 JPA 存储库在我的 Web 应用程序中实现查询。我查询的两个主要表是FmReportTb 和SpecimenTb。
这里是两个实体类(只列出了重要的属性)。
//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;
FmReportTb 与 SpecimenTb 具有 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 的一些属性以及来自SpecimenTb 的mrn。
这是我的 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 有
<property name="show_sql">true</property>。您可以添加它的休眠配置。您是否为外键的引用列添加了索引?
标签: spring hibernate rest jpa-2.0