2016-05-06 31 views
10

Ecco un POJO semplificata che ho:Jersey API + JPA/Hibernate Criteri lazy loading non funziona

@Entity 
@Table(name = "Patient") 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE) 
@DiscriminatorColumn 
(
       name="Discriminator", 
       discriminatorType=DiscriminatorType.STRING 
       ) 
@DiscriminatorValue(value="P") 
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) 
public class Patient implements Serializable{ 

    private static final long serialVersionUID = 1L; 

    @Id 
    @GeneratedValue(strategy=GenerationType.AUTO) 
    @Column(name = "ID", unique = true, nullable = false) 
    protected Integer ID; 

    @ManyToOne(targetEntity = TelephoneType.class, fetch=FetchType.LAZY, cascade = CascadeType.ALL) 
    @JoinColumn(name="IDPhoneType") 
    protected TelephoneType phoneType; 


    @JsonProperty(required=false, value="phoneType") 
    public TelephoneType getPhoneType() { 
     return phoneType; 
    } 
    public void setPhoneType(TelephoneType phoneType) { 
     this.phoneType = phoneType; 
    } 
} 

Ora qui è la mia classe TelephoneType:

@Entity 
@Table(name = "TelephoneType") 
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) 
@JsonAutoDetect(getterVisibility=Visibility.NONE, isGetterVisibility=Visibility.NONE, fieldVisibility=Visibility.NONE) 
public class TelephoneType implements Serializable{ 

private static final long serialVersionUID = -3125320613557609205L; 

@Id 
@GeneratedValue(strategy=GenerationType.AUTO) 
@Column(name = "ID", unique = true, nullable = false) 
private Integer ID; 

@Column(name = "Name") 
private String name; 

@Column(name = "Description") 
private String description; 

public TelephoneType() { 
} 

@JsonProperty(value="id") 
public int getID() { 
    return ID; 
} 

public void setID(int iD) { 
    ID = iD; 
} 

@JsonProperty(value="name") 
public String getName() { 
    return name; 
} 

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

@JsonProperty(value="description") 
public String getDescription() { 
    return description; 
} 

public void setDescription(String description) { 
    this.description = description; 
} 

}

La ragione io uso l'annotazione @JsonAutoDetect in TelephoneType è prima di personalizzare i nomi delle proprietà json (ho bisogno di disattivare il jsonautodetect predefinito) e anche perché se non lo faccio, I ge t un errore durante il recupero della coda

Nessun serializzatore trovato per la classe org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer e immobili scoperti per creare BeanSerializer (per evitare eccezioni, disabilitare SerializationFeature.FAIL_ON_EMPTY_BEANS)) (attraverso catena di riferimento: my.package.Patient [ "PHONETYPE"] -> my.package.TelephoneType _ _ $$ jvste17_13 [ "handler"])

Quindi, senza l'annotazione @JsonAutoDetect ottengo l'errore e con l'annotazione no Lazy Loading si verifica e TelephoneType viene sempre caricato nella risposta JSON.

ho utilizzare i criteri per rendere la query:

return this.entityManager.find(Patient.class, primaryKey); 

Ho anche aggiunto, come ho letto in diversi messaggi su così, quanto segue nella web.xml della mia applicazione (API Jersey):

<filter> 
    <filter-name>OpenEntityManagerInViewFilter</filter-name> 
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>OpenEntityManagerInViewFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

Ora in qualche modo ho sicuramente perso qualcosa nella mia configurazione, ma non riesco a capire cosa e abbiamo molti rapporti @ManyToOne nel dB che rallentano l'API considerevole (alcuni oggetti più pesante di quello ho mostrato nel esempio) così Mi piacerebbe davvero trovare un modo per attivare questa cosa di caricamento pigro ...

+0

Potrebbe rimuovere il filtro OpenEntityManagerInViewFilter e vedere che cosa è successo? – xsalefter

+0

se lo rimuovo, questo è il classico errore che ottengo: "Impossibile inizializzare lentamente una raccolta di ruoli: ca.chronometriq.commons.cmqmodel.Patient.TelephoneType, impossibile inizializzare il proxy - nessuna sessione (tramite la catena di riferimenti: ca. chronometriq.commons.cmqmodel .... " – jon

risposta

6

Se si utilizza JSON poi Presumo che si forniscono i risultati attraverso un endpoint REST. Quello che sta succedendo allora è il passaggio dell'entità Patient al servizio REST. Quando il servizio REST, in questo caso Jersey, serializes l'entità Patient tocca tutte le proprietà e persino le attraversa, in modo da costruire il più completo possibile. Per fare ciò, ogni volta che Jersey colpisce una proprietà non ancora inizializzata, Hibernate effettua un'altra chiamata al database. Questo è possibile solo se lo EntityManager non è stato ancora chiuso.

Ecco perché è necessario installare OpenEntityManagerInViewFilter. Senza di esso, EntityManager viene chiuso quando si esce dal livello di servizio e si ottiene LazyInitializationException. Lo OpenEntityManagerInViewFilter apre lo EntityManager a livello di vista e lo mantiene aperto fino al completamento della richiesta HTTP. Quindi, anche se sembra una correzione, non è proprio perché, come vedi, quando perdi il controllo su chi accede alle proprietà delle tue entità, in questo caso Jersey, finisci per caricare cose che non volevi caricare .

È meglio rimuovere lo OpenEntityManagerInViewFilter e capire che cosa esattamente si desidera che Jersey serializzi. Una volta capito, ci sono almeno due modi per gestirlo. IHMO, la "migliore pratica" è di avere DTO, o oggetti di trasferimento dati. Questi sono POJO che non sono entità ma hanno praticamente gli stessi campi. Nel caso, lo PatientDTO avrebbe tutto tranne la proprietà phoneType (o forse solo l'Id). Lo si passerebbe a Patient nel costruttore e si copierà i campi che si desidera vengano serializzati da Jersey. Il livello di servizio sarà quindi responsabile della restituzione di DTO anziché Entities, almeno per gli endpoint REST. I tuoi clienti otterrebbero grafici JSON che rappresentano questi DTO, offrendoti un controllo migliore su ciò che accade nel JSON perché scrivi i DTO separati dallo Entities.

Un'altra opzione è utilizzare le annotazioni JSON per impedire a Jersey di tentare di serializzare proprietà che non si desidera serializzare, come ad esempio phoneType, ma che alla fine diventa problematico. Ci saranno requisiti conflittuali e non riuscirai mai a risolverlo bene.

Anche se rendere DTO all'inizio sembra un dolore orribile, non è così male come sembra e aiuta anche quando si desidera serializzare valori più adatti al cliente. Quindi, la mia raccomandazione è quella di perdere il OpenEntityManagerInViewFilter e costruire un adeguato livello di servizio che restituisca DTO, o Visualizza oggetti come talvolta vengono chiamati.

Riferimenti: What is Data Transfer Object?

REST API - DTOs or not?

Gson: How to exclude specific fields from Serialization without annotations

+0

Questa risposta è vicina alla soluzione che abbiamo adottato mentre cercavamo di capire come caricare il pigro. Stiamo usando @JsonView con diversi livelli per dire alla maglia cosa serializzare o meno a seconda di ciò che vogliamo. Ma con questa soluzione o la soluzione DTO, non possiamo trarre vantaggio dal vantaggio prestazionale che Lazy Loading ci darebbe ... – jon

+1

Certo che puoi. Nelle tue entità imposta tutto a pigro carico e nel tuo livello di servizio recupera solo le cose che vuoi serializzate. –

+0

Ok, ci provo il lunedì – jon

3

Per capire cosa sta succedendo qui devi capire come funziona il caricamento lento in Hibernate.

Quando una lista viene dichiarata "lazy loaded", il framework Hibernate implementa un oggetto "lazy loaded" JavassistLazyInitializer con Javassist. Quindi, phoneType sull'oggetto paziente non è un'implementazione della classe TelephoneType. È un proxy verso di esso. Quando viene chiamato getPhoneType() su questo oggetto, il proxy sul paziente viene sostituito dall'oggetto reale. Sfortunatamente, @JsonAutoDetect utilizza la riflessione sull'oggetto proxy senza mai chiamare getPhoneType() e tenta di serializzare effettivamente l'oggetto JavassistLazyInitializer che, naturalmente, è impossibile.

Penso che la soluzione più elegante per questo è implementare una query che recupera i pazienti con il loro telefonoTipo.

Così, invece di:

return this.entityManager.find(Patient.class, primaryKey); 

implementare qualcosa di simile:

EntityManager em = getEntityManager(); 
CriteriaBuilder cb = em.getCriteriaBuilder(); 
CriteriaQuery<Patient> query = cb.createQuery(Patient.class); 
Root<Patient> c = query.from(Patient.class); 
query.select(c).distinct(true); 
c.fetch("phoneType"); 
TypedQuery<Patient> typedQuery = em.createQuery(query); 
List<Patient> allPatients = typedQuery.getResultList(); 

Adattare la query alle vostre esigenze, come richiesto.

+0

Non sono sicuro di capire cosa fa l'operazione 'fetch' esattamente qui.Il mio problema è che il phoneType è sempre caricato anche se il caricamento Lazy è configurato. rimuovilo da certe chiamate (quindi suppongo che questo sia fatto usando la modalità di sospensione.initialize() – jon

+0

Il fetch si assicura che sia caricato non pigramente - aggiunge un join -, mentre rimane pigro per tutte le altre chiamate. Se vuoi serializzarlo, devi recuperarlo. Altrimenti, la soluzione è di escluderla completamente dalla serializzazione. –

+0

A proposito, Patient patient = this.entityManager.find (Patient.class, primaryKey); patient.getPhoneType(); rinviare il paziente; risolverà anche il tuo problema, ma è meno elegante. –