2010-06-16 10 views
6

Se ho un rapporto con @OneToMany @Cascade (CascadeType.SAVE_UPDATE) come segueCome abilitare Hibernate Interceptor quando ho la mia Hibernate Transaction gestita da Spring?

public class One { 

    private Integer id; 

    private List<Many> manyList = new ArrayList<Many>(); 

    @Id 
    @GeneratedValue 
    public Integer getId() { 
     return this.id; 
    } 

    @OneToMany 
    @JoinColumn(name="ONE_ID", updateable=false, nullable=false) 
    @Cascade(CascadeType.SAVE_UPDATE) 
    public List<Many> getManyList() { 
     return this.manyList; 
    }   

} 

e molti classe

public class Many { 

    private Integer id; 

    /** 
     * required no-arg constructor 
     */ 
    public Many() {} 

    public Many(Integer uniqueId) { 
     this.id = uniqueId 
    } 

    /** 
     * Without @GeneratedValue annotation 
     * Hibernate will use assigned Strategy 
     */ 
    @Id 
    public Integer getId() { 
     return this.id; 
    } 

} 

se ho il seguente scenario

One one = new One(); 

/** 
    * generateUniqueId method will Take care of assigning unique id for each Many instance 
    */ 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 
one.getManyList().add(new Many(generateUniqueId())); 

E chiamo

sessionFactory.getCurrentSession().save(one); 

Prima di andare in

Secondo Transitive persistence documentazione di riferimento Hibernate, è possibile vedere

Se un genitore viene passato per salvare(), update() o saveOrUpdate(), tutti i bambini sono passati a saveOrUpdate()

ok. Ora vediamo cosa Java Persistence con Hibernate libro parla di metodo saveOrUpdate

Hibernate interroga la tabella di molti, in data id e se si trova, Hibernate aggiorna la riga. Se non viene trovato, l'inserimento di una nuova riga è obbligatorio e completato.

che può essere tradotto in base alle

INSERT INTO ONE (ID) VALUES (?) 

/** 
    * I have four Many instances added To One instance 
    * So four select-before-saving 
    * 
    * I DO NOT NEED select-before-saving 
    * Because i know i have a Fresh Transient instance 
    */ 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 
SELECT * FROM MANY WHERE MANY.ID = ? 

INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 
INSERT INTO MANY (ID, ONE_ID) VALUES (?, ?) 

Qualsiasi soluzione per evitare select-before-risparmio ??? Sì, è possibile

  • Aggiungere una colonna @Version (non applicato)
  • implementare il metodo isTransient forniti da Hibernate intercettore (L'opzione che ho)

Così come un modo per evitare il comportamento predefinito di select-before-saving quando si utilizza questo tipo di collegamento in cascata, ho migliorato il mio codice assegnando un Hibernate Interceptor a una Hibernate Session cui transazione è gestita da Spring.

Qui va il mio repository

Prima (Senza alcun Hibernate Interceptor): Funziona bene!

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.getCurrentSession().save(instance); 
    } 

} 

Dopo (con Hibernate Inteceptor): qualcosa va storto (No query SQL viene eseguita - Né INSERT Nor select-PRIMA DI RISPARMIO)

@Repository 
public class SomeEntityRepository extends AbstractRepository<SomeEntity, Integer> { 

    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public void add(SomeEntity instance) { 
     sessionFactory.openSession(new EmptyInterceptor() { 
      /** 
       * To avoid select-before-saving 
       */ 
      @Override 
      public Boolean isTransient(Object o) { 
       return true; 
      } 
     }).save(instance); 
    } 

} 

La mia domanda è: perché Primavera non lo fa persiste la mia Entità e le sue relazioni quando uso Hibernate Interceptor e cosa dovrei fare come soluzione per lavorare bene ???

risposta

3

Primavera mantiene un'associazione tra la sessione corrente e la transazione corrente (vedi SessionFactoryUtils.java.) Poiché non v'è già una sessione associata per la chiamata al metodo DAO corrente, è necessario utilizzare questa sessione, o fare il grande passo di essere coinvolto con i dettagli torbidi di associare la nuova sessione al contesto di transazione precedente. Probabilmente è possibile, ma con un rischio considerevole, ed è decisamente sconsigliato. In ibernazione, se hai già una sessione aperta, dovrebbe essere utilizzata.

Detto questo, potresti essere in grado di ottenere molla per creare una nuova sessione per te e associarla al contesto della transazione corrente. Utilizzare SessionFactoryUtils.getNewSession(SessionFactory, Interceptor). Se si utilizza questo sessionFactory anziché Hibernate, questo dovrebbe mantenere l'associazione con la transazione.

Inizialmente, è possibile codificarlo direttamente nel DAO. Quando è provato e testato, si spera che possa funzionare, è quindi possibile prendere provvedimenti per spostare il codice sorgente dal DAO, ad esempio l'utilizzo di AOP per aggiungere consigli sui metodi add() che creano e puliscono la nuova sessione.

Un'altra alternativa è utilizzare un intercettore globale. Anche se è globale, puoi dargli un comportamento controllabile localmente. TransientInterceptor contiene un threadLocal<Boolean>. Questo è il flag per il thread corrente per indicare se l'intercettore deve restituire true per isTransient. Lo si imposta su true all'inizio del metodo add() e lo si cancella alla fine. Per esempio.

class TransientInterceptor extends EntityInterceptor { 
     ThreadLocal<Boolean> transientFlag = new ThreadLocal<Boolean)(); 
     public boolean isTransient() { 
     return transientFlag.get()==Boolean.TRUE; 
     } 
     static public setTransient(boolean b) { 
      transientFlag.set(b); 
     } 
    } 

E poi nel tuo DAO:

@Override 
public void add(SomeEntity instance) { 
    try { 
     TransientInterceptor.set(true); 
     sessionFactory.getCurrentSession().save(instance); 
    } 
    finally { 
     TransientInterceptor.set(false); 
    } 
} 

È quindi possibile l'installazione TransientInterceptor come un intercettore globale sulla SessionFactory (es. LocalSessionFactoryBean) Per rendere questo meno invasiva, è possibile creare un AOP intorno consiglio applicare questo comportamento a tutti i metodi di aggiunta DAO, se necessario.

0

Nel metodo "dopo" si sta creando una nuova sessione e non lo si sta svuotando, pertanto non viene inviato alcun aggiornamento al database. Questo non ha nulla a che fare con Spring, ma è puro comportamento Hibernate.

Ciò che si desidera è aggiungere un intercettore (entità) a sessionFactory, probabilmente configurato utilizzando Spring. È quindi possibile solo mantenere il metodo add() del repository come prima. Vedere http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/orm/hibernate3/LocalSessionFactoryBean.html#setEntityInterceptor%28org.hibernate.Interceptor%29

+0

Come detto: * È possibile aggiungere un intercettore a SessionFactory *. Succede se aggiungo un Interceptor a SessionFactory, otterrò un comportamento ** globale **. Voglio solo aggiungere un Interceptor all'istanza Session che viene utilizzata dal metodo add (SomeEntity instance). Qualche soluzione? –

Problemi correlati