2014-09-09 13 views
8

Abbiamo un servizio che è @Statefull. La maggior parte dei dati-Operazioni sono atomici, ma all'interno di un certo insieme di funzioni. Vogliamo eseguire più native queries all'interno di una transazione.Hibernate nativeQuery - Transazione?

Abbiamo iniettato il EntityManager con un contesto di persistenza con ambito transazione. Quando si crea un "mazzo" di entità normali, usando em.persist() tutto funziona correttamente.

Ma quando si utilizzano le query native (alcune tabelle non sono rappresentate da qualsiasi @Entity) Hibernate non le esegue all'interno della stessa transazione ma fondamentalmente utilizza UNA transazione per query.

Così, ho già cercato di utilizzare manuali START TRANSACTION; e COMMIT; voci - ma che sembra interferire con le operazioni, Hibernate sta usando a persistere entità, quando si mescolano le query nativi e le chiamate di persistenza.

@Statefull 
class Service{ 

    @PersistenceContext(unitName = "service") 
    private EntityManager em; 

    public void doSth(){ 
     this.em.createNativeQuery("blabla").executeUpdate(); 
     this.em.persist(SomeEntity); 
     this.em.createNativeQuery("blablubb").executeUpdate(); 
    } 
} 

Tutto all'interno di questo metodo deve avvenire all'interno di una transazione. È possibile con Hibernate? Durante il debug, è chiaramente visibile che ogni affermazione avviene "indipendente" da qualsiasi transazione. (Ie I cambiamenti sono lavata al database dopo ogni affermazione.)


Ho testato il soffietto dato esempio con una configurazione minima per elimnate qualsiasi altro fattore sul problema (stringhe sono solo per i punti di interruzione a rivedere il database dopo ogni query):

@Stateful 
@TransactionManagement(value=TransactionManagementType.CONTAINER) 
@TransactionAttribute(value=TransactionAttributeType.REQUIRED) 
public class TestService { 

    @PersistenceContext(name = "test") 
    private EntityManager em; 

    public void transactionalCreation(){ 
     em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate(); 
     String x = "test"; 
     em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate(); 
     String y = "test2"; 
     em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate(); 
    } 
} 

Hibernate è configurato in questo modo:

<persistence-unit name="test"> 
     <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> 
     <jta-data-source>java:jboss/datasources/test</jta-data-source> 

     <properties> 
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" /> 

      <property name="hibernate.transaction.jta.platform" 
       value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" /> 

      <property name="hibernate.archive.autodetection" value="true" /> 
      <property name="hibernate.jdbc.batch_size" value="20" /> 
      <property name="connection.autocommit" value="false"/> 
     </properties> 
    </persistence-unit> 

E il risultato è lo stesso che con modalità autocommit: dopo ogni nat quesito, il database (rivedere il contenuto da una seconda connessione) viene aggiornato immediatamente.


L'idea di utilizzare l'operazione in modo manuall conduce allo stesso risultato:

public void transactionalCreation(){ 
     Session s = em.unwrap(Session.class); 
     Session s2 = s.getSessionFactory().openSession(); 
     s2.setFlushMode(FlushMode.MANUAL); 
     s2.getTransaction().begin(); 

     s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate(); 
     String x = "test"; 
     s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate(); 
     String y = "test2"; 
     s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate(); 

     s2.getTransaction().commit(); 
     s2.close(); 
    } 
+0

Hai provato [questo] (http://stackoverflow.com/a/12969520/2749837) già in modo? – gantners

+0

in alternativa puoi provare a interrompere l'ibernazione dalle modifiche di flussaggio al db chiamando prima getSession(). SetFlushMode (FlushMode.MANUAL), ma questa è solo un'ipotesi. – gantners

+0

per la tua risposta. 'FlushMode.Manual' non ha alcun impatto sembra. Proverò la soluzione proposta sull'uso diretto di una sessione piuttosto che su 'em'. – dognose

risposta

3

Nel caso in cui non si utilizza container managed transactions allora avete bisogno di aggiungere il criterio di transazione troppo:

@Stateful @TransactionManagement (valore = TransactionManagementType.CONTAINER) @TransactionAttribute (valore = RICHIESTA)

Ho solo visto questo fenomeno in due situazioni:

  • il DataSource è in esecuzione in modalità auto-commit, quindi ogni chiusura viene eseguito in una transazione seprated
  • l'EntityManager non è stato configurato con @Transactional, ma poi solo le query possono essere eseguite, poiché qualsiasi operazione DML finirebbe generando un'eccezione richiesta di transazione.

Ricapitoliamo aver impostato le seguenti proprietà di Hibernate:

hibernate.current_session_context_class=JTA 
transaction.factory_class=org.hibernate.transaction.JTATransactionFactory 
jta.UserTransaction=java:comp/UserTransaction 

Dove la proprietà finale deve essere impostato con il tuo UserTransaction JNDI Application Server chiave di denominazione.

Si potrebbe anche usare il:

hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup 

o altra strategia secondo la vostra corrente Application Server.

+0

Sì, Hibernate è in esecuzione in modalità di commit automatico. È configurato per utilizzare anche JbossTransactionManager. Ho già provato - come menzionato nel commento - per impostare FlushMode su Manual per un determinato metodo, che finora non ha avuto alcun effetto. Proverò ad aggiungere anche la politica delle transazioni e ad aggiornare il mio post con il risultato. – dognose

+2

Ma non dovresti eseguire il commit automatico. Hibernate dovrebbe controllare il commit o il rollback della transazione. –

+0

Ho testato la soluzione proposta, ma anche con una configurazione minima (vedi post aggiornato) Ogni query è indipendente. (Ho anche giocato con varie annotazioni sul metodo 'transactionalCreation()', non ho potuto ottenere nulla finora ... Puoi dire per certo che questo * è * possibile usando 'nateiveQuery()'? – dognose

0

Dopo aver letto l'argomento per un altro gruppo di ore mentre giocavo con ogni proprietà di configurazione e/o annotazione, ho trovato una soluzione funzionante per il mio caso. Potrebbe non essere la migliore o l'unica soluzione, ma dal momento che la domanda ha ricevuto alcuni segnalibri e upvotes, mi piacerebbe condividere quello che ho finora:

In un primo momento, non c'era modo di farlo funzionare come previsto quando si esegue l'unità di persistenza in modalità gestita. (<persistence-unit name="test" transaction-type="JTA"> - JTA è di default se nessun valore dato.)

ho deciso di aggiungere un altro persistenza a unità al xml persistenza, che è configurato per l'esecuzione in modalità non gestita: <persistence-unit name="test2" transaction-type="RESOURCE_LOCAL">.

(Nota:. Il Waring circa più unità Persistenza è giusta causa eclisse non è in grado di gestire Non ha alcun impatto funzionale a tutti)

Il gestito persitence-contesto richiede la configurazione locale del database, dal momento che non è più il contenitore fornito:

<persistence-unit name="test2" transaction-type="RESOURCE_LOCAL"> 
     <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> 

     <class>test.AEntity</class> 

     <properties> 
      <property name="hibernate.connection.url" value="jdbc:mysql://localhost/test"/> 
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" /> 
      <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> 
      <property name="hibernate.connection.password" value="1234"/> 
      <property name="hibernate.connection.username" value="root"/> 
      <property name="hibernate.hbm2ddl.auto" value="update" /> 
      <property name="hibernate.show_sql" value="true" /> 
      <property name="hibernate.archive.autodetection" value="true" /> 
      <property name="hibernate.jdbc.batch_size" value="20" /> 
      <property name="hibernate.connection.autocommit" value="false" /> 
     </properties> 
    </persistence-unit> 

un cambiamento necessario per il progetto ora sarebbe, che si aggiunge un unitName, ogni volta che si utilizza il @PersistenceContext annotazioni per recuperare un'istanza gestita del EntityManager.

Tuttavia, è possibile utilizzare solo @PersistenceContext per l'unità di persistenza gestita. Per chi non gestito, è possibile implementare un semplice Producer e iniettare l'EntityManager utilizzando CDI ad ogni richiesta:

@ApplicationScoped 
public class Resources { 

    private static EntityManagerFactory emf; 

    static { 
     emf = Persistence.createEntityManagerFactory("test2"); 
    } 

    @Produces 
    public static EntityManager createEm(){ 
     return emf.createEntityManager(); 
    } 
} 

Ora, nell'esempio citato nel post originale, è necessario iniettare l'EntityManager e manualmente prendersi cura sulle transazioni.

@Stateful 
public class TestService { 

    @Inject 
    private EntityManager em; 

    public void transactionalCreation() throws Exception { 

     em.getTransaction().begin(); 

     try { 
      em.createNativeQuery(
        "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','a')") 
        .executeUpdate(); 
      em.createNativeQuery(
        "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','b')") 
        .executeUpdate(); 
      em.createNativeQuery(
        "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')") 
        .executeUpdate(); 
      em.createNativeQuery(
        "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')") 
        .executeUpdate(); 

      AEntity a = new AEntity(); 
      a.setName("TestEntity1"); 
      em.persist(a); 

      // force unique key violation, rollback should appear. 
//   em.createNativeQuery(
//     "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')") 
//     .executeUpdate(); 
      em.getTransaction().commit(); 
     } catch (Exception e) { 
      em.getTransaction().rollback(); 
     } 
    } 
} 

miei test finora hanno mostrato che la miscelazione di query nativi e la persistenza chiama portare al risultato desiderato: o tutto si impegna o la transazione è rolledback nel suo complesso.

Per ora, la soluzione sembra funzionare. Continuerò a convalidare la sua funzionalità nel progetto principale e verificare se ci sono altri effetti collaterali.

Un'altra cosa che ho bisogno di verificare è se sarebbe Salva:

  • Iniettare entrambe le versioni del EM in un unico Bean e mescolare utilizzo.(I primi controlli sembrano funzionare, anche quando si usano entrambi gli em allo stesso tempo sullo stesso tavolo (i))
  • Avendo entrambe le versioni dell'EM che operano sulla stessa origine dati. (Fonte di dati lo stesso sarebbe molto probabilmente essere nessun problema, stesse tabelle presumo potrebbe portare a problemi imprevisti.)

ps .: Questo è Progetto 1. Continuerò a migliorare la risposta e a segnalare problemi e/o inconvenienti che troverò.

0

Devi aggiungere <hibernate.connection.release_mode key="hibernate.connection.release_mode" value="after_transaction" /> ai tuoi immobili. Dopo un riavvio, dovrebbe funzionare la gestione delle transazioni.

Problemi correlati