【问题标题】:Is this Hibernate method very inefficient?这种 Hibernate 方法效率很低吗?
【发布时间】:2015-04-25 00:40:58
【问题描述】:

此函数是我正在编写的 Hibernate 程序的一部分,用于为大学财务系统进行一些后端批量处理。当程序执行时,它会被调用几十万次,这些调用是程序中最大的时间槽。目前我想不出一种合理的方式来减少它的频率。

此函数将会计年度支付周期(“fypper”)和该周期中的一周作为参数,并返回一个构造(“AidYearTerm”),该构造存储支付周期周所在的年份、学期和期限.

public  AidYearTerm FypperTermInfo (String fypper, String week) {

    sfa_fws_calendar aidYear = new sfa_fws_calendar();      

    TypedQuery<sfa_fws_calendar> query = manager.createQuery("FROM sfa_fws_calendar cal "
            + "WHERE cal.id.fypper = ?1 AND cal.id.week_num = ?2",sfa_fws_calendar.class);
    query.setParameter(1, fypper);
    query.setParameter(2, week);

    List<sfa_fws_calendar> aidYearList = query.getResultList();

    if(!aidYearList.isEmpty()) {
        aidYear = query.getSingleResult();
    }
    else {
        aidYear.setFWS_AID_YR("9999");
        aidYear.setSEM("NOTSET");
        aidYear.setTERM("NOTSET");
        ErrorOut("Could not find term info for "+fypper);
    }
    DebugOut("found aid year "+aidYear.getFWS_AID_YR()+", term "+aidYear.getTERM());

    AidYearTerm aidYearTerm = new AidYearTerm(aidYear.getFWS_AID_YR(),aidYear.getTERM(),aidYear.getSEM()); 
    return aidYearTerm;

}

我可以做些什么来让这更简单/更快?

【问题讨论】:

  • "被调用几十万次" -- 一个数据库查询,执行了几十万次?如果除了大幅减少这个数字之外没有太多需要改进的地方,也不会感到惊讶。
  • 顺便问一下,您的数据库中有多少种年份和星期的组合? - 如果不是数百万,我认为不是,应该很容易读取一次,例如将它们存储在HashMap 中,然后对这些缓存数据进行操作。
  • 大约有 11,000 种组合。在将它们存储在哈希图中并引用它而不是重新查询之后,过去需要两个小时的过程现在需要大约十五分钟。谢谢!
  • 很高兴听到这个消息。我将在我的答案中添加方法...

标签: java sql oracle performance hibernate


【解决方案1】:

乍一看:

List<sfa_fws_calendar> aidYearList = query.getResultList();

if(!aidYearList.isEmpty()) {
    aidYear = query.getSingleResult(); // <========= DON'T!!!!!
}

Query.getSingleResult() 再次运行相同的查询!

用途:

List<sfa_fws_calendar> aidYearList = query.getResultList();

if(!aidYearList.isEmpty()) {
    aidYear = aidYearList.get(0);
}

编辑:

假设您没有按年和周键入的数百万条记录,将所有需要的数据从 sfa_fws_calendar 读入内存一次似乎是可行的,例如读入Map,以避免数千次往返到数据库。

请注意,因为fypperweek 实际上形成了一个(字符串)复合键,所以您可以构建一个Map&lt;String, AidYearTerm&gt;,其中的键可以是例如year + "_" + week。那么你的FypperTermInfo (String fypper, String week) 将只是return aidYearCacheMap.get(fypper + "_" + week); 和voilà :)

【讨论】:

    【解决方案2】:

    最简单和最明显的优化是这样的:

    static final TypedQuery<sfa_fws_calendar> query = manager.createQuery("FROM sfa_fws_calendar cal "
            + "WHERE cal.id.fypper = ?1 AND cal.id.week_num = ?2", sfa_fws_calendar.class);
    
    public AidYearTerm FypperTermInfo(String fypper, String week) {
    
        //...
        query.setParameter(1, fypper);
        query.setParameter(2, week);
    
        List<sfa_fws_calendar> aidYearList = query.getResultList();
    

    不确定调用createQuery 的开销,但这肯定只会发生一次。

    在那之后 - 如果一切仍然在爬行(这很可能)你可以考虑某种形式的缓存(也许ehcache)但只有当fypper|week组合数量相对较少时才会有效.

    【讨论】:

      【解决方案3】:

      这看起来是一个非常简单的选择,所以我认为这可能更多的是确保 Oracle 高效运行它(特别是因为它每天运行数千次)。对于这个查询,您看到了什么样的解释计划?有全表扫描吗?您可能需要索引 sfa_fws_calendar 表,或者如果已经有索引,请更改适当的索引并重新计算索引统计信息。

      例如,这样的事情可能有助于更新名为 index_name 的现有索引:

      ALTER INDEX index_name REBUILD COMPUTE STATISTICS;
      

      【讨论】: