2014-12-17 15 views
5

Sto scrivendo una query JPQL (con Hibernate come mio provider JPA) per recuperare un'entità Company e diverse sue associazioni. Questo funziona bene con le mie associazioni ManyToMany "semplici", in questo modo:Partecipazione al recupero nested con JPQL e Hibernate

@Entity 
@Table(name = "company") 
@NamedQueries({ 
     @NamedQuery(
       name = "Company.profile.view.byId", 
       query = "SELECT c " + 
         "FROM Company AS c " + 
         "INNER JOIN FETCH c.city AS city " + <-- @ManyToOne 
         "LEFT JOIN FETCH c.acknowledgements " + <-- @ManyToMany 
         "LEFT JOIN FETCH c.industries " + <-- @ManyToMany 
         "WHERE c.id = :companyId" 
     ) 
}) 
public class Company { ... } 

Hibernate crea una singola query per recuperare quanto sopra, che è buono. Tuttavia, la mia entità Company ha anche un'associazione molti a molti con i dati memorizzati nella tabella intermedia, quindi perché questa è mappata come associazioni @OneToMany e @ManyToOne tra tre entità.

società < - CompanyService -> Servizio

Questi sono i tre soggetti che ho nel mio codice. Pertanto, un'istanza Company ha una raccolta di entità CompanyService, ciascuna delle quali ha una relazione con un'istanza Service. Spero che abbia senso - altrimenti controlla il codice sorgente alla fine della domanda.

Ora desidero recuperare i servizi per una determinata azienda modificando la query precedente. Ho letto in anticipo che JPA non consente join fetch annidati o alias pari per join, ma che alcuni provider JPA lo supportano, quindi ho tentato la fortuna con Hibernate. Ho provato a modificare la query in quanto tale:

@Entity 
@Table(name = "company") 
@NamedQueries({ 
     @NamedQuery(
       name = "Company.profile.view.byId", 
       query = "SELECT c " + 
         "FROM Company AS c " + 
         "INNER JOIN FETCH c.city AS city " + 
         "LEFT JOIN FETCH c.acknowledgements " + 
         "LEFT JOIN FETCH c.industries " + 
         "LEFT JOIN FETCH c.companyServices AS companyService " + 
         "LEFT JOIN FETCH companyService.service AS service " + 
         "WHERE c.id = :companyId" 
     ) 
}) 
public class Company { ... } 

Ora, invece di creare una singola query, Hibernate crea le seguenti interrogazioni:

#1 
select ... 
from company company0_ 
inner join City city1_ on company0_.postal_code = city1_.postal_code 
[...] 
left outer join company_service companyser6_ on company0_.id = companyser6_.company_id 
left outer join service service7_ on companyser6_.service_id = service7_.id 
where company0_.id = ? 

#2 
select ... 
from company company0_ 
inner join City city1_ on company0_.postal_code = city1_.postal_code 
where company0_.id = ? 

#3 
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_ 
from service service0_ 
where service0_.id = ? 

#4 
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_ 
from service service0_ 
where service0_.id = ? 

Query # 1 ho lasciato fuori il irrilevante join come questi sono OK. Sembra selezionare tutti i dati di cui ho bisogno, inclusi i servizi e i dati delle entità intermedie (CompanyService).

Query # 2 Questa query recupera semplicemente l'azienda dal database e la sua City. L'associazione cittadina è stata recuperata con impazienza, ma la query viene comunque generata anche se la cambio in pigro. Onestamente, non so a cosa serva questa domanda.

Query # 3 + Query # 4 Queste query sono alla ricerca fino Service istanze basata su ID, presumibilmente sulla base di ID di servizio recuperati in Query # 1. Non vedo la necessità di questa query, perché questi dati sono stati già recuperati in Query n. 1 (proprio come i dati di Query n. 2 erano già stati scaricati in Query n. 1). Inoltre, questo approccio ovviamente non si adatta bene se una società ha molti servizi.

La cosa strana è che sembra che la query n. 1 faccia ciò che voglio, o almeno recuperi i dati di cui ho bisogno. Solo non so perché Hibernate crea la query n. 2, n. 3 e n. Quindi ho le seguenti domande:

  • Perché Hibernate crea query n. 2, n. 3 e n. 4? E posso evitarlo?
  • Hibernate supporta il recupero di associazioni nidificate anche se JPA non lo fa? Se è così, come potrei farlo nel mio caso?
  • Questo comportamento è normale, oppure è perché quello che sto cercando di fare non è supportato e quindi ottengo risultati strani?Ciò sembrerebbe strano, perché la query n. 1 sembra perfetta

Qualsiasi suggerimento di errori o soluzioni alternative per realizzare ciò che voglio sarebbe molto apprezzato. Di seguito è riportato il mio codice (getter e setter esclusi). Grazie mille in anticipo!

entità impresa

@Entity 
@Table(name = "company") 
@NamedQueries({ 
     @NamedQuery(
       name = "Company.profile.view.byId", 
       query = "SELECT c " + 
         "FROM Company AS c " + 
         "INNER JOIN FETCH c.city AS city " + 
         "LEFT JOIN FETCH c.acknowledgements " + 
         "LEFT JOIN FETCH c.industries " + 
         "LEFT JOIN FETCH c.companyServices AS companyService " + 
         "LEFT JOIN FETCH companyService.service AS service " + 
         "WHERE c.id = :companyId" 
     ) 
}) 
public class Company { 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column 
    private int id; 

    // ... 

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = City.class, optional = false) 
    @JoinColumn(name = "postal_code") 
    private City city; 

    @ManyToMany(fetch = FetchType.LAZY) 
    @JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id")) 
    private Set<Acknowledgement> acknowledgements; 

    @ManyToMany(fetch = FetchType.LAZY) 
    @JoinTable(name = "company_industry", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "industry_id")) 
    private Set<Industry> industries; 

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company") 
    private Set<CompanyService> companyServices; 
} 

entità CompanyService

@Entity 
@Table(name = "company_service") 
@IdClass(CompanyServicePK.class) 
public class CompanyService implements Serializable { 
    @Id 
    @ManyToOne(targetEntity = Company.class) 
    @JoinColumn(name = "company_id") 
    private Company company; 

    @Id 
    @ManyToOne(targetEntity = Service.class) 
    @JoinColumn(name = "service_id") 
    private Service service; 

    @Column 
    private String description; 
} 

entità Servizio

@Entity 
@Table(name = "service") 
public class Service { 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column 
    private int id; 

    @Column(length = 50, nullable = false) 
    private String name; 

    @Column(name = "default_description", nullable = false) 
    private String defaultDescription; 
} 

dati Recuperare

public Company fetchTestCompany() { 
    TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class); 
    query.setParameter("companyId", 123); 

    return query.getSingleResult(); 
} 

risposta

1

Ok, sembra che l'ho capito. Impostando il tipo di recupero suin CompanyService, Hibernate ha interrotto la generazione di tutte le query ridondanti che sostanzialmente recuperavano gli stessi dati. Ecco la nuova versione dell'entità:

@Entity 
@Table(name = "company_service") 
@IdClass(CompanyServicePK.class) 
public class CompanyService implements Serializable { 
    @Id 
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class) 
    @JoinColumn(name = "company_id") 
    private Company company; 

    @Id 
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = Service.class) 
    @JoinColumn(name = "service_id") 
    private Service service; 

    @Column 
    private String description; 
} 

La query JPQL rimane la stessa.

Tuttavia, nel mio caso particolare con il numero di associazioni della mia entità Company, ho ricevuto molti dati duplicati e quindi è stato più efficiente consentire a Hibernate di eseguire una query aggiuntiva. Ho realizzato questo rimuovendo i due fetch join dalla mia query JPQL e cambiando il mio codice di query al di sotto.

@Transactional 
public Company fetchTestCompany() { 
    TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class); 
    query.setParameter("companyId", 123); 

    try { 
     Company company = query.getSingleResult(); 
     Hibernate.initialize(company.getCompanyServices()); 

     return company; 
    } catch (NoResultException nre) { 
     return null; 
    } 
} 

inizializzando l'associazione companyServices, Hibernate esegue un'altra query per andare a prendere i servizi. Nel mio caso d'uso particolare, è meglio che recuperare una tonnellata di dati ridondanti con una query.

Spero che questo aiuti qualcuno. Se qualcuno ha delle soluzioni/miglioramenti migliori, ovviamente sarei felice di ascoltarli.

0

Da quello che hai scritto, direi che il recupero nidificato non è supportato. Questa è la mia comprensione dei risultati:

  • Query # 1 è ok, e si unisce tutto ciò di cui ha bisogno, questo è un bene
  • Tuttavia, Query # 2 penso ottiene CompanyService#company (con ansiosa city con conseguente inner join City)
  • Query # 3 ottiene CompanyService#service
  • Query # 4 è un mistero per me

So che questa non è una risposta, ma potrebbe aiutarti a capire cosa sta succedendo in background.

+0

Grazie.Quello che mi chiedo è il motivo per cui le ultime tre domande vengono persino eseguite, quando il primo è tutto Hibernate deve fare quello che gli ho chiesto di fare. Ma forse hai ragione che non è supportato - ma se così fosse, allora è strano che assembla correttamente # 1 per fare esattamente quello che mi aspettavo. Strano! :-) – Andy0708

+0

Strano che sia, ma sfortunatamente non posso aiutare di più, e queste sono solo le mie ipotesi. Vorrei vedere un commento da qualcuno che ha familiarità con il funzionamento interno di Hibernate. –