Подтвердить что ты не робот

Hibernate HQL присоединяется к выборке, не рекурсивная выборка

У меня есть следующий запрос и метод

private static final String FIND = "SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId";

@Override
public Domain find(Long domainId) {
    Query query = getCurrentSession().createQuery(FIND);
    query.setLong("domainId", domainId);
    return (Domain) query.uniqueResult();
}

С Domain как

@Entity
@Table
public class Domain {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "domain_id")
    private Long domainId;

    @Column(nullable = false, unique = true)
    @NotNull
    private String name;

    @Column(nullable = false)
    @NotNull
    @Enumerated(EnumType.STRING)
    private DomainType type;

    @OneToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    }, fetch = FetchType.EAGER)
    @JoinTable(joinColumns = {
            @JoinColumn(name = "domain_id")
    }, inverseJoinColumns = {
            @JoinColumn(name = "code")
    })
    @NotEmpty
    @Valid // needed to recur because we specify network codes when creating the domain
    private Set<NetworkCode> networkCodes = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(joinColumns = {
            @JoinColumn(name = "parent", referencedColumnName = "domain_id")
    }, inverseJoinColumns = {
            @JoinColumn(name = "child", referencedColumnName = "domain_id")
    })
    private Set<Domain> operators = new HashSet<>();
    // more
}

Я ожидал бы, что этот единственный запрос извлечет отношения Set<NetworkCode> и Set<Domain >, но это не так. Скажем, у запроса Domain я есть два оператора, Hibernate будет выполнять 1 + 2 * 2 = 5 запросов

Hibernate: select distinct domain0_.domain_id as domain1_1_0_, domain2_.domain_id as domain1_1_1_, networkcod4_.code as code2_2_, domain0_.name as name1_0_, domain0_.type as type1_0_, domain2_.name as name1_1_, domain2_.type as type1_1_, operators1_.parent as parent1_0__, operators1_.child as child4_0__, networkcod3_.domain_id as domain1_1_1__, networkcod3_.code as code5_1__ from domain domain0_ left outer join domain_operators operators1_ on domain0_.domain_id=operators1_.parent left outer join domain domain2_ on operators1_.child=domain2_.domain_id inner join domain_network_codes networkcod3_ on domain0_.domain_id=networkcod3_.domain_id inner join network_code networkcod4_ on networkcod3_.code=networkcod4_.code where domain0_.domain_id=?
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=?
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=?
Hibernate: select operators0_.parent as parent1_1_, operators0_.child as child4_1_, domain1_.domain_id as domain1_1_0_, domain1_.name as name1_0_, domain1_.type as type1_0_ from domain_operators operators0_ inner join domain domain1_ on operators0_.child=domain1_.domain_id where operators0_.parent=?
Hibernate: select networkcod0_.domain_id as domain1_1_1_, networkcod0_.code as code5_1_, networkcod1_.code as code2_0_ from domain_network_codes networkcod0_ inner join network_code networkcod1_ on networkcod0_.code=networkcod1_.code where networkcod0_.domain_id=?

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

Могу ли я выполнить HQL-запрос, который бы выполнял обе задачи?

4b9b3361

Ответ 1

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

SELECT DISTINCT domain FROM Domain domain 
  LEFT OUTER JOIN FETCH domain.operators operators1 
  LEFT OUTER JOIN FETCH domain.networkCodes 
  LEFT OUTER JOIN FETCH operators1.operators operators2 
  LEFT OUTER JOIN FETCH operators1.networkCodes
WHERE domain.domainId = :domainId

Ответ 2

Спящий режим работает с различными стратегиями выборки..!!

Hibernate предоставляет 4 стратегии для извлечения данных:

SELECT

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id") 
@Fetch(FetchMode.SELECT)

В этом методе запускаются несколько SQL-запросов. Этот первый уволен для извлечения всех записей в родительской таблице. Оставшиеся для записи записей для каждой родительской записи. Это в основном проблема N + 1. Первый запрос извлекает N записей из базы данных, в этот случай N родительских записей. Для каждого родителя возвращается новый запрос Ребенок. Поэтому для N родительских запросов N запросов извлекают информацию из Таблица детей.

JOIN

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@Fetch(FetchMode.JOIN) 

Это похоже на стратегию выборки SELECT, за исключением того факта, что все извлечение базы данных происходит заранее в JOIN fetch, в отличие от SELECT где это происходит по необходимости. Это может стать важным эффективности.

подвыбор

 @OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
 @Column(name="id")
 @Fetch(FetchMode.SUBSELECT)

Запущены два SQL. Один для извлечения всех родительских, а второй - SUBSELECT в предложении WHERE для получения всего дочернего элемента, который имеет сопоставление идентификаторов родительских элементов.

ПАРТИИ

@OneToMany(mappedBy="tableName", cascade=CascadeType.ALL)
@Column(name="id")
@@BatchSize(size=2)

Размер партии сопоставляется с числом родителя, чей ребенок извлекается. Таким образом, мы можем указать количество записей, которые будут выбраны за раз. Будут выполняться несколько запросов.!!

один-ко-многим и многие-ко-многим разрешает - join, Select и SubSelect

много-к-одному и один-к-одному позволяет - Join и Select


Hibernate также различает (когда выбраны ассоциации)

1. Непосредственная выборка -

связь, сбор или атрибут извлекаются немедленно, когда Родитель загружен. (Ленивая = "ложь" )

2. Lazy collection fetching -

сбор извлекается, когда приложение вызывает операцию это коллекция. (Это значение по умолчанию для коллекций. (Lazy = "true" )

3. Экстра-ленивый "выбор коллекции -

Доступ к отдельным элементам коллекции осуществляется из базы данных по мере необходимости. Hibernate пытается не собирать всю коллекцию в памяти, если это абсолютно необходимо (подходит для очень больших коллекций) (Ленивый = "экстра" )

4. Получение прокси-сервера -

однозначная ассоциация извлекается, когда метод, отличный от идентификатор getter вызывается на ассоциированный объект. (Ленивый = "прокси" )

5. Без прокси-сервера "-

однозначная связь выбирается, когда переменная экземпляра доступ. По сравнению с выборкой прокси, этот подход менее ленив. (Lazy = "no-proxy" )

6. Lazy выбор атрибута -

атрибут или однозначная связь извлекается, когда экземпляр доступ к переменной. (Ленивая = "истина" )

один-ко-многим и многие-ко-многим позволяет Immediate, Layzy, Extra Lazy

много-к-одному и один-к-одному позволяет Immediate Proxy, No Proxy

Ответ 3

Вы отметили ассоциации EAGER. Итак, что бы вы ни делали в своем запросе, Hibernate загрузит все связанные домены и сетевые коды загруженных доменов. И он будет загружать домены и сетевые коды дополнительных доменов и т.д. И т.д., Пока все нагрузки коллекции не возвратят пустые коллекции или объекты, которые уже были загружены.

Чтобы этого избежать, сделайте свои коллекции ленивыми (как по умолчанию). Тогда загрузка домена с его операторами и его сетевые коды будут загружать именно это.

Ответ 4

Ваше сопоставление EAGER будет считаться автоматически только Hibernate, если вы используете API критериев для запроса.

Если вы используете HQL, вам нужно вручную добавить ключевое слово FETCH в свои JOIN, чтобы заставить Hibernate включать отношения в первый запрос и избежать последующих запросов.

Это спецификация Hibernate и может работать по-другому на других ORM.

Смотрите этот вопрос/ответ для немного другого угла.

Ответ 5

Не задокументировано это хорошо, но попытались ли вы установить FetchMode? Вы можете сделать это, используя API-интерфейс Criteria: domainCriteria.setFetchMode("operators", JOIN) или используйте @Fetch(JOIN) в определении отношения.

Аннотации (и только аннотация, как кажется) также позволяет установить режим выборки SUBSELECT, который должен по крайней мере сдерживать Hibernate для выполнения 3 запросов max. Не зная ваш набор данных, я предполагаю, что это должен быть способ пойти на вас, так как большое жирное соединение над этими столами не кажется слишком здоровым. Лучше всего понять это, я думаю...

Ответ 6

Поскольку вы уже указали FetchType.EAGER для networkCodes и operators, при каждом запросе domain hibernate будет загружать как networkCodes и operators. Вот и вся идея режима EAGER

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

private static final String FIND
    = "SELECT DISTINCT domain"
    + " FROM Domain domain"
    + " WHERE domain.domainId = :domainId";

Подробности API здесь

Ура !!

Ответ 7

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

Однако вы можете сообщить Hibernate использовать стратегию выборки как sub select, если вы не хотите использовать объединения.

Hibernate генерирует SQL-запрос для загрузки объектов во время запуска на основе указанных сопоставлений и кэширует его. Однако в вашем случае у вас есть одно-много вложенных отношений с самим собой и произвольная глубина, поэтому похоже, что hibernate не сможет решить перед рукой sql для корректной загрузки. Поэтому ему нужно будет отправить несколько запросов на объединение в зависимости от глубины родительского домена, который вы запрашиваете во время выполнения.

Мне кажется, вы думаете, что HQL и результирующий SQL/(s) в вашем случае могут иметь один к одному, который не является правда. С помощью HQL вы запрашиваете объекты, и orm решает, как загрузить этот объект и его отношения (нетерпеливые/ленивые) на основе или вы можете указать их во время выполнения (например, ленивая ассоциация при сопоставлении может быть переопределена Query api, но не наоборот). Вы можете сказать orm, что загружать (моя маркировка нетерпеливая или ленивая) и как загружать с нетерпением (либо с помощью выбора соединения/суб).

UPDATE

Когда я запускаю следующий запрос в вашей модели домена

SELECT DISTINCT domain FROM Domain domain LEFT OUTER JOIN FETCH domain.operators LEFT OUTER JOIN FETCH domain.networkCodes WHERE domain.domainId = :domainId";

Я вижу, что коллекции networkCode и operator имеют экземпляр PersistentSet (это оболочка Hibernate), и оба имеют инициализированное свойство, которое установлено как true. Также в базовом контексте сеанса я вижу области с доменом и перечисленными операторами. Итак, что заставляет вас думать, что они не загружены?

Так мой домен выглядит как

@Entity
@Table
public class Domain {
    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "domain_id")
    private Long domainId;

    @Column(nullable = false, unique = true)   
    private String name;

    @Column(nullable = false)    
    @Enumerated(EnumType.STRING)
    private DomainType type;

    @OneToMany(mappedBy = "domain",cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE
    }, fetch = FetchType.EAGER)   
    private Set<NetworkCode> networkCodes = new HashSet<NetworkCode>();

    @ManyToMany(mappedBy="parent",fetch = FetchType.EAGER, cascade=CascadeType.ALL)
    private Set<Domain> operators = new HashSet<Domain>();
    // more

    @ManyToOne  
    private Domain parent;

    public String getName() {
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


public DomainType getType() {
        return type;
    }

    public void setType(DomainType type) {
        this.type = type;
    }


    public Set<Domain> getOperators() {
        return operators;
    }


    public Long getDomainId() {
        return domainId;
    }


    public void setDomainId(Long domainId) {
        this.domainId = domainId;
    }


    public void setOperators(Set<Domain> operators) {
        this.operators = operators;
    }

    public void addDomain(Domain domain){
        getOperators().add(domain);
        domain.setParent(this);
    }


    public Domain getParent() {
        return parent;
    }


    public void setParent(Domain parent) {
        this.parent = parent;
    }

    public void addNetworkCode(NetworkCode netWorkCode){
        getNetworkCodes().add(netWorkCode);
        netWorkCode.setDomain(this);
    }

enter image description here