web-dev-qa-db-fra.com

Le regroupement Hibernate HQL n'effectue pas l'extraction récursive

J'ai la requête et la méthode suivantes

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

Avec Domain comme

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

Je m'attendrais à ce que cette requête extrait les relations Set<NetworkCode> et Set<Domain>, mais ce n'est pas le cas. Dire que la requête Domain I possède deux opérateurs, Hibernate exécuterait 1 + 2 * 2 = 5 requêtes

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=?

J'imagine que c'est parce que je rejoins les éléments Domain des opérateurs, mais ils doivent se joindre eux-mêmes. 

Existe-t-il une requête HQL que je pourrais exécuter qui ferait les deux?

12

Si vous savez que vous n'avez que deux niveaux dans votre arbre, avez-vous pensé à rejoindre un niveau plus profond. Quelque chose comme ci-dessous?

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
11
Andrei I

Les relations Hibernate fonctionnent avec différentes stratégies d'extraction .. !!

Hibernate propose 4 stratégies pour récupérer des données:

S&EACUTE;LECTIONNEZ

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

Dans cette méthode, plusieurs instructions SQL sont déclenchées. Le premier est déclenché Pour récupérer tous les enregistrements de la table parent. Les autres sont activés pour récupérer des enregistrements pour chaque enregistrement parent. C’est fondamentalement Le problème N + 1. La première requête extrait N enregistrements de la base de données, dans Ce cas, N enregistrements parent. Pour chaque parent, une nouvelle requête extrait Enfant. Par conséquent, pour N parent, les requêtes N extraient des informations de la table Child.

REJOINDRE

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

Cette stratégie est similaire à la stratégie d’extraction SELECT, sauf que toutes les récupérations de base de données Ont lieu au début de l’extraction JOIN, contrairement à SELECT , Où cela se produit au besoin. Cela peut devenir un facteur de performance important .

SOUS-S&EACUTE;LECTIONNER

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

Deux SQL sont déclenchés. Le premier permet d'extraire tous les parents et le second utilise une requête SUBSELECT dans la clause WHERE pour extraire tous les enfants dont l'identifiant parent Correspond.

LOT

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

La taille du lot correspond au nombre de parents dont l'enfant est récupéré. Nous pouvons donc spécifier le nombre d'enregistrements à récupérer à la fois.Mais Plusieurs requêtes seront exécutées. !!

un-plusieurs-plusieurs et plusieurs-plusieurs permet - rejoindre, sélectionner et SubSelect

plusieurs-à-un et un-à-un permettent - Joindre et sélectionner


Hibernate fait également la distinction entre (quand les associations sont récupérées)

1. Récupération immédiate

une association, une collection ou un attribut est récupéré immédiatement lorsque le parent est chargé. (paresseux = "faux")

2. Récupération de collection paresseuse

une collection est extraite lorsque l'application appelle une opération sur cette collection. (Ceci est la valeur par défaut pour les collections. (Lazy = “true”)

3. " Extra-paresseux " collection à récupérer - 

les éléments individuels de la collection sont accessibles à partir de la base de données selon les besoins. Hibernate essaie de ne pas récupérer la totalité de la collection dans la mémoire de Sauf en cas d'absolue nécessité (convient pour les très grandes collections) (Lazy = "extra")

4. Récupération de proxy

une association à valeur unique est extraite lorsqu'une méthode autre que le getter d'identificateur est invoquée sur l'objet associé. (lazy = "proxy")

5. " No-proxy " chercher - 

une association à valeur unique est extraite lorsque la variable d'instance est accessible à . Comparée à la récupération de proxy, cette approche est moins paresseuse. (Lazy = “no-proxy”)

6. Lazy attribution d'attributs - 

un attribut ou une association à valeur unique est récupéré lors de l'accès à la variable d'instance . (paresseux = "vrai")

un-à-plusieurs et plusieurs-à-plusieurs permet Immediate, Layzy, Extra Lazy

plusieurs-à-un et un-à-un permet un proxy immédiat, aucun proxy

13
Dileep

Vous avez marqué vos associations EAGER. Ainsi, quoi que vous fassiez dans votre requête, Hibernate chargera tous les domaines et codes de réseau associés des domaines chargés. Et il chargera les domaines et les codes de réseau des domaines supplémentaires, etc., etc. jusqu'à ce que tous les chargements de collection renvoient des collections vides ou des entités déjà chargées.

Pour éviter cela, rendez vos collections paresseuses (comme elles le sont par défaut). Ensuite, charger un domaine avec ses opérateurs et ses codes de réseau ne chargera que cela.

3
JB Nizet

Votre mappage EAGER ne sera automatiquement pris en compte par Hibernate que si vous utilisez l'API Criteria pour la requête.

Si vous utilisez HQL, vous devrez ajouter manuellement le mot clé FETCH à vos JOINs pour forcer Hibernate à inclure les relations dans la première requête et éviter les requêtes suivantes.

Ceci est spécifique à Hibernate et peut fonctionner différemment sur d'autres ORM.

Voir cette question/réponse pour un angle légèrement différent.

2
sola

Ce n'est pas bien documenté, mais avez-vous essayé de définir FetchMode ? Vous pouvez le faire en utilisant l'API Criteria: domainCriteria.setFetchMode("operators", JOIN) ou en utilisant @Fetch(JOIN) dans la définition de relation.

L'annotation (et seulement l'annotation comme il semble) permet également de définir un mode de récupération SUBSELECT, ce qui devrait au moins restreindre Hibernate à exécuter 3 requêtes max. Ne connaissant pas votre ensemble de données, je suppose que cela devrait être votre solution, car une grosse jointure sur ces tables ne semble pas très saine. Le mieux est de le comprendre, je suppose ...

1
skirsch

Puisque vous avez déjà spécifié FetchType.EAGER pour networkCodes et operators, chaque fois que vous interrogerez domain, hibernate chargera les deux networkCodes et operators. C'est toute l'idée du mode de récupération EAGER

Donc, vous pouvez changer votre requête simple en suivant:

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

Détails de l'API ici

À votre santé !!

0
Sachin Thapa

Ma première observation est qu'il n'est pas nécessaire d'écrire une requête HQL contenant des jointures si vos mappages indiquent qu'ils doivent être chargés avec impatience.

Vous pouvez toutefois demander à Hibernate d'utiliser la stratégie de récupération en tant que sous-sélection si vous ne souhaitez pas utiliser de jointure.

Hibernate génère la requête SQL pour le chargement des objets au démarrage en fonction des mappages spécifiés et le met en cache. Cependant, dans votre cas, vous avez une relation imbriquée avec soi et une profondeur arbitraire , il semble donc qu'il ne soit pas possible pour l'hibernation de décider avant Que le sql soit correctement récupéré. Par conséquent, il faudrait envoyer plusieurs requêtes de jointure en fonction de la profondeur du domaine parent interrogé au moment de l'exécution.

Pour moi, il semble que vous pensiez que HQL et le résultat SQL/(') résultant dans votre cas peuvent avoir une correspondance qui n’est pas Vraie. Avec HQL, vous interrogez des objets et l'orm décide comment charger cet objet et ses relations (empressé/paresseux) en fonction des mappages Ou vous pouvez également les spécifier à l'exécution (par exemple, une association paresseuse dans le mappage peut Query api (mais pas l’inverse). Vous pouvez indiquer à l’Orm ce qu’il doit charger (mon marquage vif ou paresseux) et comment le charger rapidement (soit en utilisant join/sub select).

METTRE &AGRAVE; JOUR

Lorsque j'exécute la requête suivante sur votre modèle de domaine

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

Je peux voir que les collections networkCode et operator correspondent à l'instance PersistentSet (il s'agit du wrapper Hibernate) et que les deux propriétés initialisées sont définies sur true. De plus, dans le contexte de session sous-jacent, je peux voir les domaines avec le domaine et les opérateurs répertoriés. Alors, qu'est-ce qui vous fait penser qu'ils ne sont pas chargés avec impatience?

Voici à quoi ressemble mon domaine

@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

0
Shailendra