Как получить поле лениво с помощью Hibernate Criteria

Каждая строка таблицы Person (имеющая name, firstname и age) должна быть прочитана.

EntityManager em = emf.createEntityManager();
Session s = (Session) em.getDelegate();
Criteria criteria = s.createCriteria(Person.class);
criteria.setFetchMode("age", FetchMode.SELECT);

Но SQL показывает

Hibernate:
    select
        person0_.name,
        person0_.firstname,
        person0_.age
    from 
        SCOPE.PERSON person0_

Как разрешить возрасту быть ленивым ТОЛЬКО для критериев

Ответы

Ответ 1

Я думаю, что ленивый режим имеет смысл только с ассоциациями. Если вы получаете доступ к простой таблице, он загрузит все поля.

Если вы хотите, чтобы поле age не отображалось в SQL и поэтому не загружалось в память, используйте прогнозы:

Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);

Ответ 2

Настройка FetchMode свойства age для критерия не имеет никакого эффекта, поскольку стратегия выборки в этой точке относится только к связанным объектам, но не к свойствам. См. Раздел 20.1. Извлечение стратегий в hibernate docs.

Hibernate использует стратегию выборки для извлечения связанных объектовесли приложение должно перемещаться по ассоциации. Стратегии выбора могут быть объявлены в метаданных сопоставления O/R или перечеркнуты конкретный запрос HQL или Criteria.

Единственный способ для ленивой загрузки свойства - это аннотация @Basic, установленная на FetchType.LAZY. См. здесь, или если вы используете файлы .hbm.xml для отображения, используйте lazy=true, см. этот раздел документов спящего режима.

Аннотация @Basic позволяет объявить стратегию выбора для свойство. Если установлено значение LAZY, указано, что это свойство должно быть лениво возникает при первом обращении к переменной экземпляра. Это требует встроенного инструментария байт-кода, если ваши классы не являются инструмент, уровень собственности ленивая загрузка молча игнорируется.

В ленивой загрузке свойств также используется создание байтового кода buildtime (hibernate меняет классы сущностей после компиляции, чтобы позволить ленивую загрузку свойств). Прочтите 20.1.8. Использование ленивой выборки свойств

Другим возможным решением (кроме всех других решений) для вашей проблемы является создание более простого класса Person и использование запроса конструктора, например:

public class PersonDTO {
    private String name;
    private String firstname;

    private Person(String name, String firstname) {
        this.name = name;
        this.firstname = firstname;
    }
    // getters & setters
}

Query q = session.createQuery("select new your.package.name.PersonDTO("
    + "p.name, p.firstname) from Person p");
q.list();

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

Но все представленные здесь решения не реализуют ленивую загрузку атрибута age. Единственный способ сделать это - аннотация @Basic, или вы должны выполнить свою собственную ленивую загрузку.

Ответ 3

Ваше обоснование действительно (в общем, мы можем, однако, рассуждать о конкретном примере поля age), но, к сожалению, для этого нет прямого решения. На самом деле, Hibernate имеет концепцию выборки профилей, но в настоящее время она очень ограничена (вы можете переопределить план/стратегию выборки по умолчанию только с помощью команды join- стиль профилей выборки).

Таким образом, возможный обходной путь к вашей проблеме может быть следующим.

1) Переместите age в отдельный объект и привяжите объект Person к нему с ленивым отношением один к одному:

@Entity
class PersonAge {
   private Integer age;
}

@Entity
class Person {
   @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
   @JoinColumn(name = "PERSON_AGE_ID")
   private PersonAge personAge;

   public Integer getAge() {
      return personAge.getAge();
   }

   public void setAge(Integer age) {
      personAge.setAge(age);
   }
}

2) Определите профиль выборки, который переопределяет значение по умолчанию:

@FetchProfile(name = "person-with-age", fetchOverrides = {
   @FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN)
})

3) Включите этот профиль для каждого сеанса в приложении:

session.enableFetchProfile("person-with-age");

В зависимости от используемой структуры должен быть легкий крюк/перехватчик, который вы будете использовать для включения профиля для каждого сеанса (транзакции), который преследуется. Например, подход в Spring может заключаться в переопределении AbstractPlatformTransactionManager.doBegin используемого менеджера транзакций.

Таким образом, personAge будет загружен во всех сеансах в приложении, если явно не отключен профиль выборки.

4) Отключите профиль выборки в сеансе, в котором вы используете запрос Критерии:

session.disableFetchProfile("person-with-age");

Таким образом используется план/стратегия выборки по умолчанию (указанная в сопоставлениях сущностей), которая представляет собой ленивую загрузку personAge.

Ответ 4

Если ваш возраст - это объект, такой как PersonAge @Dragan, вы можете связать fecth с критериями, а не с сущностью, как вы.

Итак, я думаю, у вас есть три варианта:

  • возраст как примитивный и проекционный, как @Paco говорит (Person.age будет null, а не прокси, вы потеряете ленивость, что вы хотите)
  • возраст как примитивный без проекции (больше байтов в проводе)
  • age как PersonAge + criteria.setFetchMode(вы получите ленивость, которую хотите получить за счет дополнительного объекта/таблицы/сопоставления)

Для Projection вы можете использовать ResultTransformer для

Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
crit.setResultTransformer(new ResultTransformer() {

      @Override
      public Object transformTuple(Object[] tuple, String[] aliases) {
        String name = (Long) tuple[0];
        String firstName = (String) tuple[1];
        return new Person(name , firstName);
      }

      @Override
      public List<Reference> transformList(List collection) {
        return collection;
      }
    });

Я думаю, вы могли бы создать собственный PersonProxy, который запускает запрос для получения возраста, но это довольно ужасно.

  @Override
  public Object transformTuple(Object[] tuple, String[] aliases) {
    String name = (Long) tuple[0];
    String firstName = (String) tuple[1];
    return new PersonProxy(name , firstName);
  }

  class PersonProxy {
    Person realPerson;

    public getAge(){
       // create a query with realPerson.id for retrieve the age. 
    }
  }

Ответ 5

Вы можете просто определить новый объект SimplePerson, сопоставленный с той же таблицей базы данных persons, которая содержит только следующие атрибуты:

  • ID
  • имя
  • ПгвЬЫате

Таким образом, при выборе SimplePerson как с критериями, так и с HQL возрастный столбец не будет получен.

Другой альтернативой является использование ленивой загрузки для основных атрибутов, но отображение нескольких сущностей в одну и ту же таблицу базы данных намного более гибко.