2017-02-03 13 views
0

В настоящее время у нас есть класс, который выглядит примерно так, что (обезличенных и нерелевантные части удалены):Hibernate аннотации для одного выбора объекта (с которой), а не на-ко-многим коллекции

@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(), в основном для отображения строки внутри MainTableState -класс в пользовательском интерфейсе.

Кроме того, даже если мы всегда будем использовать TreeSet и сохраним его, чтобы мы не нуждались в цикле, но вместо этого нужно только states.iterator().next(), он все равно инициализирует список состояний. С некоторыми тяжелыми испытаниями производительности у нас было более 1 миллиона MainTableState -в случае, когда он разбился с java.lang.OutOfMemoryError: GC overhead limit exceeded.

Итак, мы хотим, чтобы изменить его на следующий вместо:

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

    @??? 
    private MainTableState activeState; 

    ... 

    public MainTableStates getActiveState(){ 
    return activeState; 
    } 

    ... 
} 

Итак, мой вопрос, что я должен положить на @??? для достижения этой цели? Я предполагаю, что мне нужен @Formula или что-то подобное, но как я могу сказать, чтобы спящий режим должен вернуть объект MainTableState? Я видел, что @Formula используется с MAX для даты, но это должно было получить эту дату-свойство, а не получить весь объект на основе этой максимальной даты.


После @ user2447161 «s предложение я использовал @Where -annotation, который действительно помогает уменьшить размер сбора до 1 (иногда), но у меня есть еще два взаимосвязанных вопроса:

  1. Как использовать @OnToMany и @Where, но получить один объект, а не список объектов одного размера? Возможно ли это? Here in a answer from December 2010 it is stated it isn't. Было ли это исправлено где-то за последние шесть лет?

  2. Как бороться со случайным псевдонимом в предложении where? Я мог бы сделать что-то вроде этого:

    @OneToMany (скачивает = FetchType.LAZY, mappedBy = "mainTable") @Where (п = «CREATED_ON = (SELECT MAX (mts.CREATED_ON) FROM MAIN_TABLE_STATES мтс WHERE mts.FK_MAIN_ID = ???. MAIN_ID) ") private Set states; // TODO Получить один объект вместо коллекции с размером 1

Проблема с в том, что ??? случайный псевдоним генерируется спящий режим (иногда это 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 
     ) 
    ) 
+0

Если у вас нет безумного количества строк, индексов и/или очень строгих требований к производительности, я не вижу, чтобы это было проблемой производительности. С современными компьютерами цикл for, как правило, занимает nano секунд, а загрузка набора, а не одной строки, должна быть незначительной. Также убедитесь, что список нетерпеливы, когда он используется, есть значительные накладные расходы с ленивыми -loading (вероятно, на несколько больше, чем фактический цикл). – Tobb

+0

@Tobb Я добавил еще одну строку к вопросу. Цикл for-loop не является основной проблемой, это превышение верхнего предела «java.lang.OutOfMemoryError: GC overhead» с более чем 1 миллионом экземпляров MainTableState. Это основная причина, по которой мы хотим просто сохранить одно активное состояние, а не целые коллекции (которые в настоящее время не используются). –

+1

Возможно, проверьте @where и фильтры ... http://stackoverflow.com/questions/12365285/hibernate-limit-query-with-one-to-many. Или сделайте свой собственный DTO вне пользовательского запроса ... – user2447161

ответ

0

Ну, что касается вашего вопроса № 2, как это выглядит, как вам нужно быстрое решение с минимальным воздействием на существующий код, это может быть приемлемым: вы можете использовать Interceptor иметь дело с псевдонимом и генерировать right sql statement. Сделайте это:

  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; }

Имейте в виду, что этот метод будет вызываться для каждого SQL заявление, что спящий режим создает в вашем приложении.

Additionaly, ваша статья @Where работает только правильно, когда объединение используется, так что вы должны установить режим выборки явно добавляя @Fetch(FetchMode.JOIN) к states собственности, чтобы избежать этого спящего режима может использовать режим выбора.

 Смежные вопросы

  • Нет связанных вопросов^_^