2015-01-26 11 views
7

Sto utilizzando Spring Data JPA 1.7.1 con Hibernate 4.3.7 come provider JPA. Ho la seguente repository primavera dati JPA:NPE in dati primari JPA con clausola IN

@Repository 
public interface CompanyRepository extends JpaRepository<Company, Integer> { 
    @EntityGraph(value = "graph.company.search.results", type = EntityGraph.EntityGraphType.FETCH) 
    @Query("SELECT c FROM Company c WHERE c.id IN :companyIds") 
    List<Company> findByCompanyIdsForSearchResults(@Param("companyIds") Set<Integer> companyIds); 
} 

Il codice seguente invoca il metodo repository di cui sopra:

Set<Integer> companyIds = new HashSet<>(); 
companyIds.add(100000); 
// companyIds.add(100001); // This line breaks the code 
List<Company> companies = this.companyRepository.findByCompanyIdsForSearchResults(companyIds); 

sto sperimentando strano comportamento con quanto sopra. Innanzitutto, se inserisco solo un ID nel set, le due istanze dueCompany vengono restituite nell'elenco, anche se l'ID è ovviamente univoco. In secondo luogo, se aggiungo più di un ID al set, quindi il codice non riesce con il seguente NullPointerException:

java.lang.NullPointerException 
    org.hibernate.param.NamedParameterSpecification.bind(NamedParameterSpecification.java:67) 
    org.hibernate.loader.hql.QueryLoader.bindParameterValues(QueryLoader.java:616) 
    org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1901) 
    org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1862) 
    org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1839) 
    org.hibernate.loader.Loader.doQuery(Loader.java:910) 
    org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:355) 
    org.hibernate.loader.Loader.doList(Loader.java:2554) 
    org.hibernate.loader.Loader.doList(Loader.java:2540) 
    org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2370) 
    org.hibernate.loader.Loader.list(Loader.java:2365) 
    org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:497) 
    org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:387) 
    org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:236) 
    org.hibernate.internal.SessionImpl.list(SessionImpl.java:1264) 
    org.hibernate.internal.QueryImpl.list(QueryImpl.java:103) 
    org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:573) 
    org.hibernate.jpa.internal.QueryImpl.getResultList(QueryImpl.java:449) 
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    java.lang.reflect.Method.invoke(Method.java:483) 
    org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:362) 
    com.sun.proxy.$Proxy217.getResultList(Unknown Source) 
    org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:110) 
    org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74) 
    org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:98) 
    org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:89) 
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421) 
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.data.repository.core.support.RepositoryFactorySupport$DefaultMethodInvokingMethodInterceptor.invoke(RepositoryFactorySupport.java:512) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) 
    org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) 
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 
    org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) 
    com.sun.proxy.$Proxy177.findByCompanyIdsForSearchResults(Unknown Source) 

    ... 

Ho anche provato a cambiare il set a un elenco, ma con lo stesso risultato. La clausola WHERE della query generata è simile alla seguente: where company0_.id in (?)

Sto facendo qualcosa di sbagliato, o si tratta di un bug? Il motivo per cui sto usando @Query invece di nominare il mio metodo findByIdIn è che voglio la libertà di assegnare al mio metodo un nome univoco a seconda del contesto (perché il grafo dell'entità dipende dal contesto in cui viene invocato il metodo). Quello che sto cercando di fare è supportato?

Grazie in anticipo.

Modifica n. 3: Si scopre che l'eccezione viene generata a causa dell'annotazione @EntityGraph. Questo è abbastanza strano perché ho usato i grafici delle entità con altri metodi di repository che hanno un'annotazione @Query e tutto ha funzionato bene. Sembra, tuttavia, come se la combinazione di @EntityGraph e della clausola IN causi problemi. Sembra essere un bug. Se rimuovo la clausola IN e cambio il metodo per cercare una singola azienda, tutto funziona correttamente con il grafico delle entità. Qualcuno ha qualche idea per una soluzione o soluzione? Potrei "manualmente" JOIN FETCH le mie associazioni, ma questo non è così bello come usare un grafico di entità.

Modifica n. 2: È interessante notare che tutto funziona come previsto se scrivo un metodo di repository personalizzato as described here. Il seguente codice funziona benissimo:

public List<Company> test(Set<Integer> companyIds) { 
    String jpql = "select c from Company c where c.id in :companyIds"; 
    Query q = this.getEntityManager().createQuery(jpql); 
    q.setParameter("companyIds", companyIds); 
    List results = q.getResultList(); // Contains X entities, and works with > 1 company IDs as well 

    return null; 
} 

A quanto pare il problema ha a che fare con l'implementazione automatica di primavera dati JPA del mio metodo di interfaccia. I potrebbe solo utilizzare un'implementazione "personalizzata", ma sarebbe molto più bello se potessi utilizzare il primo approccio, quindi sono ancora alla ricerca di una soluzione al problema originale.

Modifica n. 1: Di seguito è riportato il codice sorgente dell'entità Company (esclusi getter e setter).

@Entity 
@Table(name = "company", uniqueConstraints = { 
     @UniqueConstraint(columnNames = { "slug" }) 
}) 
@NamedEntityGraphs({ 
     @NamedEntityGraph(
       name = "graph.company.profile.view", 
       attributeNodes = { 
         @NamedAttributeNode(value = "city"), 
         @NamedAttributeNode(value = "acknowledgements"), 
         @NamedAttributeNode(value = "industries"), 
         @NamedAttributeNode(value = "companyServices", subgraph = "companyServices") 
       }, 
       subgraphs = { 
         @NamedSubgraph(name = "companyServices", attributeNodes = { 
           @NamedAttributeNode(value = "service") 
         }) 
       } 
     ), 

     @NamedEntityGraph(
     name = "graph.company.search.results", 
     attributeNodes = { 
       @NamedAttributeNode(value = "city"), 
       @NamedAttributeNode(value = "acknowledgements"), 
       @NamedAttributeNode(value = "industries"), 
       @NamedAttributeNode(value = "companyServices", subgraph = "companyServices") 
     }, 
     subgraphs = { 
       @NamedSubgraph(name = "companyServices", attributeNodes = { 
         @NamedAttributeNode(value = "service") 
       }) 
     } 
) 
}) 
public class Company { 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column 
    private int id; 

    @NotEmpty 
    @Length(min = 5, max = 100) 
    @Column(length = 100, nullable = false) 
    private String name; 

    @Length(min = 3, max = 50) 
    @Column(length = 50) 
    private String slug; 

    @Column 
    private Double rating; 

    @Column(name = "number_of_reviews") 
    private int numberOfReviews; 

    @Length(min = 5, max = 50) 
    @Column(name = "street_name", length = 50) 
    private String streetName; 

    @Length(max = 25) 
    @Column(name = "street_number", length = 25) 
    private String streetNumber; 

    @Length(min = 8, max = 11) 
    @Column(name = "phone_number", length = 11) 
    private String phoneNumber; 

    @Length(min = 8, max = 11) 
    @Column(name = "second_phone_number", length = 11) 
    private String secondPhoneNumber; 

    @Length(min = 50, max = 175) 
    @Column 
    private String teaser; 

    @Length(min = 50, max = 5000) 
    @Column 
    private String description; 

    @Length(min = 8, max = 8) 
    @Column(name = "ci_number", nullable = false) 
    private long ciNumber; 

    @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")) 
    @OrderBy("name") 
    private Set<Acknowledgement> acknowledgements = new HashSet<Acknowledgement>(); 

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

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

    @OneToMany(fetch = FetchType.LAZY, targetEntity = Review.class, mappedBy = "company") 
    private Set<Review> reviews = new HashSet<Review>(); 

    @OneToMany(fetch = FetchType.LAZY, targetEntity = Like.class, mappedBy = "company") 
    private Set<Like> likes = new HashSet<Like>(); 

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company") 
    private Set<AccountCompany> accountCompanies = new HashSet<AccountCompany>(); 
} 
+0

La clausola in funziona senza parentesi graffe - è necessario rimuoverli. Solo le vecchie versioni di Hibernate lo richiedevano ed era sempre un bug che faceva. E se si utilizza un altro tipo di dati, ad esempio un elenco? Storicamente, il tipo di oggetto previsto per il parametro di una clausola è sempre un po 'schizzinoso. – Gimby

+0

@Gimby Il risultato è lo stesso se utilizzo una lista. Ho messo la parentesi perché ho avuto un errore se non l'avessi fatto. Si scopre che l'errore è lo stesso 'NullPointerException' che ottengo sopra. Ma senza parentesi nella clausola 'IN', ottengo questo anche se ho solo un intero in Lista/Set. Se aggiungo la parentesi, si verifica solo con> 1 numeri interi. Roba funky. Li rimuoverò, ma l'eccezione NPE è ancora generata. – Andy0708

+0

Funky davvero. Puoi provare a non usare la molla, ma solo richiamare una normale query JPA usando un gestore di entità per vedere se almeno funziona come previsto? – Gimby

risposta

3

provare con un parametro indicizzato, invece, e senza la @EntityGraph:

@Query("SELECT c FROM Company c WHERE c.id IN ?1") 
List<Company> findByCompanyIdsForSearchResults(Set<Integer> companyIds); 

Se funziona con il parametro index, prova ad aggiungere il @EntityGraph e vederlo lavorare.

Per quanto riguarda l'EntityGraph, è necessario essere consapevoli del problema prodotto cartesiano si sta generando:

@NamedAttributeNode(value = "acknowledgements"), 
@NamedAttributeNode(value = "industries"), 
@NamedAttributeNode(value = "companyServices", subgraph = "companyServices") 

Tutte queste associazioni sono uno-a-molti rapporti, in modo che quando si recupera l'Azienda si finirà con un prodotto cartesiano tra:

  • impresa
  • Riconoscimento
  • Industria
  • CompanyService

Questo eseguirà molto male e si dovrebbe sempre JOIN FETCH at most one one-to-many association.

Suggerisco di rimuovere EntityGraph e utilizzare un JOIN FETCH per tutte le associazioni molti a uno e al massimo uno a molti.

Per inizializzare le altre associazioni si potrebbe o:

+0

Si scopre che l'eccezione viene generata a causa dell'annotazione '@ EntityGraph'. Senza di esso, i risultati vengono caricati correttamente. Questo è strano perché ho usato l'annotazione con altri metodi di repository che hanno l'annotazione '@ Query', e ha funzionato bene. Ma sembra che qualcosa vada storto quando è usato in combinazione con la clausola 'IN'. Immagino che questo sia un bug. Immagino di poter usare 'JOIN FETCH' nella mia query, ma l'approccio del grafo delle entità è molto più pulito. Avete qualche idea per una soluzione/soluzione? – Andy0708

+0

Controlla la mia risposta aggiornata. –

+0

Il motivo per cui ho accettato di riprendere il prodotto cartesiano della mia entità e delle sue associazioni è che il numero di oggetti (righe) in ogni raccolta sarà basso (1-5 per la maggior parte di essi), perché questo è limitato dalla mia logica aziendale . Non l'ho provato, ma stavo pensando che il sovraccarico di creazione del prodotto cartesiano non fosse poi così male rispetto all'emissione di più query sul mio database. Non sono sicuro esattamente a che punto un approccio sia più veloce dell'altro, poiché dovrei testarlo ulteriormente. Grazie mille per aver menzionato questo. – Andy0708

5

Dopo aver affrontato lo stesso problema e googling un po ', ho trovato è un bug di Hibernate: https://hibernate.atlassian.net/browse/HHH-9230

Ma non funziona Sembra che ci stiano lavorando ... non ci sono assegnatari di questo numero :-(

Sembra che le persone di Hibernate non si preoccupino troppo della loro implementazione JPA ... hanno alcuni bug vivi per a lungo e sembra che non abbiano intenzione di risolverli :-(

+0

Grazie per l'informazione! Molto utile sapere che è davvero un bug in Hibernate e non sulla mia fine. Sfortunatamente ho notato che il bug è stato segnalato a metà 2014, quindi probabilmente ci vorrà molto tempo prima che venga risolto. :-( – Andy0708

+1

Questo non è l'atteggiamento giusto nei confronti dei progetti OSS: Hibernate è open source, quindi dovremmo tutti contribuire. Il fatto che Red Hat paghi per qualche sviluppo, non significa che non dobbiamo essere coinvolti. –

Problemi correlati