【问题标题】:Hibernate annotation for single select object (with where) instead of on-to-many collection单个选择对象(带有位置)而不是多对集合的休眠注释
【发布时间】:2017-02-03 08:06:40
【问题描述】:

目前我们有一个看起来像这样的类(删除了去个性化和不相关的部分):

@Entity
@Table(name = "MAIN_TABLE")
public class MainTable extends AbstractTable {

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
  @OrderBy("CREATED_ON DESC")
  private Set<MainTableState> states;

  ...

  public MainTableState getActiveState(){
    if(this.states == null || this.states.isEmpty()){
      return null;
    }
    MainTableState latest = states.iterator().next();
    // The reason we use this for-loop, even though we have the @OrderBy annotation,
    // Is because we can later add states to this list, which aren't automatically ordered
    for(MainTableState state : states){
      if(state.getCreatedOn() != null && latest.getCreatedOn() != null &&
           state.getCreatedOn().after(latest.getCreatedOn()){
        latest = state;
      }
    }
    return latest;
  }

  ...
}

所以目前默认情况下它会从数据库中检索所有 MainTableStates,如果我们需要 activeState,我们使用 for-loop 方法。显然,这对性能非常不利。目前我们根本不使用这个列表(目的是有一个状态的历史,但这已经被推迟到未来),但我们确实使用了getActiveState()方法相当多,主要是为了显示一个字符串里面UI 中的MainTableState-class。

此外,即使我们总是使用TreeSet 并保持排序,这样我们就不需要循环而只需要states.iterator().next(),它仍然会初始化状态列表。通过一些繁重的性能测试,当java.lang.OutOfMemoryError: GC overhead limit exceeded 崩溃时,我们有超过 100 万个 MainTableState-instances。

因此,我们想将其更改为以下内容:

@Entity
@Table(name = "MAIN_TABLE")
public class MainTable extends AbstractEntity {

  @???
  private MainTableState activeState;

  ...

  public MainTableStates getActiveState(){
    return activeState;
  }

  ...
}

那么,我的问题是,我应该在@??? 中添加什么来完成此操作?我假设我需要 @Formula 或类似的东西,但我怎么能说休眠它应该返回一个 MainTableState 对象?我已经看到 @FormulaMAX 一起用于日期,但那是为了获取该日期属性,而不是根据该最大日期获取整个对象。


@user2447161 的建议之后,我使用了 @Where-annotation,这确实有助于将 Collection 大小减少到 1(有时),但我还有两个相关的问题:

  1. 如何使用@OnToMany@Where 但获得单个对象,而不是大小为1 的对象列表?这甚至可能吗? Here in a answer from December 2010 it is stated it isn't.这个问题在过去六年的某个地方修复了吗?

  2. 如何处理where子句中的随机别名?我可以这样做:

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable") @Where(clause = "CREATED_ON = (SELECT MAX(mts.CREATED_ON) FROM MAIN_TABLE_STATES mts WHERE mts.FK_MAIN_ID = ???.MAIN_ID)") 私有集合状态; // TODO 获取单个对象而不是大小为 1 的集合

问题在于??? 是hibernate 生成的随机别名(有时是this_,有时类似于mainTable_1_,等等)。如何为数据库的整个查询设置此别名以在此处使用它?我还尝试了MAIN_TABLE.MAIN_ID,但它不起作用,并且没有别名它也不起作用,因为它使用MainTableState-alias 而不是MainTable-alias(如下所示)。

from
    MAIN_TABLE this_ 
left outer join
    MAIN_TABLE_STATUSES mainstat2_ 
        on this_.main_id=mainstat2_.fk_main_id 
        and (
            mainstat2_.created_on = (
                SELECT
                    MAX(mts.created_on) 
            FROM
                MAIN_TABLE_STATUSES mts 
            WHERE
-- mainstat2_.main_id should be this_.main_id instead here:
                mts.fk_main_id = mainstat2_.main_id
        )
    )

【问题讨论】:

  • 除非你有大量的行,没有索引和/或在性能方面有非常严格的要求,否则我真的看不出这将是一个性能问题。对于现代计算机,for 循环通常只需纳秒,加载一组而不是单行应该可以忽略不计。此外,请确保列表在使用时是预先获取的,惰性会带来相当大的开销- 加载(可能比实际循环大一些数量级..)
  • @Tobb 我在问题中添加了另一行。 for 循环不是主要问题,而是 java.lang.OutOfMemoryError: GC overhead limit exceeded 有超过 100 万个 MainTableState-instances。这就是为什么我们只想保存单个活动状态而不是整个集合(当前未使用)的主要原因。
  • 也许检查@where 和过滤器...stackoverflow.com/questions/12365285/…。或者从自定义查询中创建自己的 DTO...

标签: java oracle hibernate max hibernate-annotations


【解决方案1】:

好吧,关于您的问题 #2,看起来您需要一个对现有代码影响最小的快速解决方案,这可能是可以接受的:您可以使用 Interceptor 处理别名并生成正确的 sql陈述。这样做:

  1. 在 @Where 子句中使用唯一字符串作为别名占位符,例如: ...WHERE mts.FK_MAIN_ID = ${MAIN_TABLE_ALIAS}.MAIN_ID...

  2. 如果您的应用程序还没有拦截器,请创建一个扩展 EmptyInterceptor 的拦截器类并将其配置为 SessionFactory 拦截器

  3. 覆盖 onPrepareStatement 方法以将占位符替换为在“来自 MAIN_TABLE”之后找到的别名,如下所示:

    public String onPrepareStatement(String sql) { String modifiedSql = sql; if (sql.contains("${MAIN_TABLE_ALIAS}")) { String mainTableAlias = findMainTableAlias(sql); modifiedSql = sql.replace("${MAIN_TABLE_ALIAS}", mainTableAlias); } return modifiedSql; }

请注意,hibernate 在您的应用程序中生成的每个 sql 语句都会调用此方法。

另外,您的 @Where 子句仅在使用连接时才能正常工作,因此您应该明确设置获取模式添加 @Fetch(FetchMode.JOIN)states 属性,以避免休眠可能使用选择模式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2012-07-15
    • 1970-01-01
    相关资源
    最近更新 更多