2010-04-20 7 views
22

Utilizzo il gestore transazioni JPA standard per le mie transazioni JPA. Tuttavia, ora voglio aggiungere alcune entità JDBC che condivideranno la stessa 'origine dati'. Come posso rendere transazionali le operazioni JDBC con la transazione primaverile? Devo passare ai gestori delle transazioni JTA? È possibile utilizzare sia il servizio transazionale JPA & JDBC con la stessa origine dati? Ancora meglio, è possibile mescolare queste due transazioni?Che gestore delle transazioni devo usare per il modello JBDC Quando si usa JPA?

UPDATE: @Espen:

Ho un dao estesa da SimpleJdbcDaoSupport che utilizza getSimpleJDBCTemplate.update per inserire una riga di database. Quando viene generata una RuntimeException dal codice del servizio, la transazione non viene mai ripristinata quando si utilizza JPATransactionManager. Esegue il rollback quando si utilizza DatasourceTransactionManager. Ho provato a eseguire il debug di JPATransactionManager e sembra che non esegua mai il rollback sul sottostante JDBCConnection (credo sia dovuto al fatto che l'origine dati non deve necessariamente essere JDBC per JPA). La configurazione della mia configurazione è esattamente come hai spiegato qui.

Qui sono i miei codici di prova:

<context:property-placeholder location="classpath:*.properties"/> 

<!-- JPA EntityManagerFactory --> 
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="persistenceProvider"> 
     <bean class="org.hibernate.ejb.HibernatePersistence" /> 
    </property> 

</bean> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory"/> 
</bean> 

<!-- 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
--> 

<!-- Database connection pool --> 
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
    <property name="driverClassName" value="${database.driverClassName}" /> 
    <property name="url" value="${database.url}" /> 
    <property name="username" value="${database.username}" /> 
    <property name="password" value="${database.password}" /> 
    <property name="testOnBorrow" value="${database.testOnBorrow}" /> 
    <property name="validationQuery" value="${database.validationQuery}" /> 
    <property name="minIdle" value="${database.minIdle}" /> 
    <property name="maxIdle" value="${database.maxIdle}" /> 
    <property name="maxActive" value="${database.maxActive}" /> 
</bean> 




<!-- Initialize the database --> 
<!--<bean id="databaseInitializer" class="com.vantage.userGroupManagement.logic.StoreDatabaseLoader"> 
    <property name="dataSource" ref="storeDataSource"/> 
</bean>--> 

<!-- ANNOTATION SUPPORT --> 

<!-- Enable the configuration of transactional behavior based on annotations --> 
<tx:annotation-driven transaction-manager="transactionManager"/> 

<!-- JPA annotations bean post processor --> 
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/> 

<!-- Exception translation bean post processor (based on Repository annotation) --> 
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> 

<!-- throws exception if a required property has not been set --> 
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/> 


<bean id="userService" class="com.rfc.example.service.UserServiceImpl"> 
    <property name="userDao" ref="userDao"></property> 
    <property name="contactDao" ref="contactDao"></property> 
    <property name="callRecordingScheduledProgramTriggerDAO" ref="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO"></property> 
</bean> 

<bean id="userDao" class="com.rfc.example.dao.UserDaoJPAImpl" /> 

<bean id="contactDao" class="com.rfc.example.dao.ContactDaoJPAImpl"></bean> 

<bean id="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO" class="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAOJDBCImpl"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 

e qui è il DAO:

@Transactional 
public class CallRecordingScheduledProgramTriggerDAOJDBCImpl extends SimpleJdbcDaoSupport implements CallRecordingScheduledProgramTriggerDAO{ 
    private static final Log log = LogFactory.getLog(CallRecordingScheduledProgramTriggerDAOJDBCImpl.class); 

@SuppressWarnings("unchecked") 
public CallRecordingScheduledProgramTrigger save(
     CallRecordingScheduledProgramTrigger entity) { 
    log.debug("save -> entity: " + entity); 



    String sql = null; 
    Map args = new HashMap(); 

    String agentIdsString = getAgentIdsString(entity.getAgentIds()); 


    String insertSQL = "insert into call_recording_scheduled_program_trigger" + 
      "  ( queue_id, queue_id_string, agent_ids_string, caller_names, caller_numbers, trigger_id, note, callcenter_id, creator_id_string, creator_id) " + 
      " values(:queueId, :queueIdString, :agentIdsString, :callerNames, :callerNumbers, :triggerId, :note, :callcenterId , :creatorIdString, :creatorId )"; 

    args.put("queueId", entity.getQueueId()); 
    args.put("agentIdsString",agentIdsString); 
    args.put("callerNames", entity.getCallerNames());  
    args.put("queueIdString", entity.getQueueIdString()); 
    args.put("callerNumbers", entity.getCallerNumbers()); 
    args.put("triggerId", entity.getTriggerId()); 
    args.put("note", entity.getNote()); 
    args.put("callcenterId", entity.getCallcenterId()); 
    args.put("creatorId", entity.getCreatorId()); 
    args.put("creatorIdString", entity.getCreatorIdString()); 

    sql = insertSQL; 
    getSimpleJdbcTemplate().update(sql, args); 
    System.out.println("saved: ----------" + entity); 
    return entity; 
} 

} 

Ecco il codice del client che chiama il dao e lancia un'eccezione (servizio di primavera)

@Transactional(propagation=Propagation.REQUIRED) 
public void jdbcTransactionTest() { 
    System.out.println("entity: "); 
    CallRecordingScheduledProgramTrigger entity = new CallRecordingScheduledProgramTrigger(); 

    entity.setCallcenterId(10L); 
    entity.setCreatorId(22L); 
    entity.setCreatorIdString("sajid"); 
    entity.setNote(System.currentTimeMillis() + ""); 
    entity.setQueueId(22); 
    entity.setQueueIdString("dddd"); 
    String triggerId = "id: " + System.currentTimeMillis(); 
    entity.setTriggerId(triggerId); 
    callRecordingScheduledProgramTriggerDAO.save(entity); 

    System.out.println("entity saved with id: " + triggerId); 

    throw new RuntimeException(); 
} 

NOTA: il codice funziona come previsto quando si utilizza DatasourceTransactionManager

AGGIORNAMENTO - 2:

Ok ho trovato la causa principale del problema. Grazie a Espen.

La mia configurazione del gestore entità era come questo (copiato dalla primavera pet-clinica app):

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="persistenceProvider"> 
     <bean class="org.hibernate.ejb.HibernatePersistence" /> 
    </property> 

</bean> 

Poi ho cambiato in questo modo:

<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="dataSource" ref="dataSource"/> 

    <property name="jpaVendorAdapter"> 
     <bean 
      class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
     <property name="showSql" value="true" /> 
     <property name="generateDdl" value="true" /> 
     <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect" /> 
    </bean> 

</property> 
</bean> 

Ora tutto sembra funzionare! Qualcuno può spiegare la differenza tra questi due approcci?

+0

forse relativa http://stackoverflow.com/questions/2650409/implement-custom-jta-xaresource-for-using-with-hibernate/2651580#2651580 – ewernli

+0

Provare a rimuovere la proprietà persistenceXmlLocation. È un'alternativa alla proprietà dataSource. I requisiti per JpaTransactionManager per lavorare con le query JPA e JDBC sono che il proprio entityManager utilizza la stessa origine dati delle query JDBC e che si specifica il dialetto JPA come si è già fatto. – Espen

risposta

25

E 'possibile mescolare JPA e JDBC codice nella stessa transazione utilizzando il JpaTransactionManager.

Un frammento dal Spring 3 di JavaDoc:

questo gestore delle transazioni supporta anche accesso DataSource diretta all'interno di una transazione (vale a dire il codice JDBC pianura a lavorare con lo stesso DataSource). Ciò consente servizi di miscelazione che consentono l'accesso a JPA e servizi che utilizzano il JDBC normale (senza essere a conoscenza di JPA)!

Si dovrebbe essere consapevoli, tuttavia, che JPA memorizza nella cache le query ed esegue tutte loro alla fine di una transazione. Quindi, se si desidera mantenere alcuni dati all'interno di una transazione con JPA e quindi recuperare i dati con JDBC, non funzionerà senza lo svuotamento esplicito del contesto di persistenza di JPA prima di tentare di recuperarlo con il codice JDBC.

Un esempio di codice che afferma con codice JDBC che il codice APP cancellato una riga all'interno di una transazione:

@Test 
@Transactional 
@Rollback(false) 
public void testDeleteCoffeeType() { 

    CoffeeType coffeeType = coffeeTypeDao.findCoffeeType(4L); 
    final String caffeForte = coffeeType.getName(); 

    coffeeTypeDao.deleteCoffeeType(coffeeType); 
    entityManager.flush(); 

    int rowsFoundWithCaffeForte = jdbcTemplate 
     .queryForInt("SELECT COUNT(*) FROM COFFEE_TYPES where NAME = ?", 
      caffeForte); 
    assertEquals(0, rowsFoundWithCaffeForte); 
} 

E se si preferisce utilizzare la classe JpaTemplate, basta sostituire il entityManager.flush() con jpaTemplate.flush();

In risposta al commento di Sajids: Con Spring è possibile configurare un gestore transazioni che supporta sia JPA che JDBC in questo modo:

<tx:annotation-driven transaction-manager="transactionManager" /> 

<!-- Transaction manager --> 
<bean id="transactionManager" class="org.springframework.orm.jpa 
      .JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory" /> 
</bean> 

e annotazione-Driven versione

@Bean 
public JpaTransactionManager transactionManager(EntityManagerFactory emf) { 
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); 
    jpaTransactionManager.setEntityManagerFactory(emf); 
    return jpaTransactionManager; 
} 

Per farlo funzionare, le query JDBC deve essere eseguito con la JdbcTemplate o la classe SimpleJdbcTemplate. Nel tuo caso con il DAO che estende SimpleJdbcDaoSupport, dovresti usare il metodo getSimpleJdbcTemplate (..).

Infine, per consentire a due metodi DAO di partecipare alla stessa transazione, chiamare entrambi i metodi DAO da un metho della classe di servizio annotato con @Transactional. Con l'elemento <tx:annotation-driven> nella tua configurazione, Spring gestirà la transazione per te con il gestore delle transazioni specificato.

Sul livello di business:

public class ServiceClass {.. 

@Transactional 
public void updateDatabase(..) { 
    jpaDao.remove(..); 
    jdbcDao.insert(..); 
} 
} 

Edit 2: Poi qualcosa è sbagliato. Funziona esattamente come specificato nel Javadoc. Il gestore dell'entità ha una proprietà dell'origine dati come il mio bean di seguito? Funzionerà solo finché inietterai la stessa origine dati nel gestore di entità e nelle tue classi estese di JpaDaoSupport.

<bean id="entityManagerFactoryWithExternalDataSoure" primary="true" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor 
       .HibernateJpaVendorAdapter" /> 
    </property> 
    <property name="jpaProperties"> 
     <value> 
      hibernate.format_sql=true 
     </value> 
    </property> 
</bean> 
+0

@Espen Per la risposta. Tuttavia, sono più interessato all'implementazione autonoma di JDBC Dao. Ad esempio, ho UserJopeImpl per User e OrderDaoJDBCImpl per Order. OrderDaoJDBCImpl estende 'SimpleJdbcDaoSupport'. il mio primo problema, come faccio a fare ordine transitorioJDBCImpl transazionale (dichiarazione o annotazione o xml). Devo scrivere un altro manager transazionale? il mio secondo problema. Come mescolare queste due transazioni? Potrei fare questo mix in transazioni EJB 2.1 CMT vs BMT (usa JTA). Ma con Spring & JPA, è possibile senza JTA? – Sajid

+0

@Sajid: le transazioni dichiarative funzionano quasi allo stesso modo con Spring come con EJB 3. Invece di @TransactionAttribute si utilizza invece @Transactional. E sì, è sicuramente possibile senza JTA e con Spring. Richiede solo che tutte le query vengano eseguite contro la stessa origine dati. A – Espen

+0

Ho un esteso dao da SimpleJdbcDaoSupport che utilizza getSimpleJDBCTemplate.update per inserire una riga del database. Quando viene generata una RuntimeException dal codice del servizio, la transazione non viene mai ripristinata quando si utilizza JPATransactionManager. Esegue il rollback quando si utilizza DatasourceTransactionManager. Ho provato a eseguire il debug di JPATransactionManager e sembra che non esegua mai il rollback su JDBCConnection sottostante (suppongo che l'origine dati non debba necessariamente essere JDBC per JPA). La configurazione della mia configurazione è esattamente come hai spiegato qui. – Sajid

0

Non ho ancora lavorato in dettaglio nei dettagli, poiché non ho combinato né JDBC né JPA, ma se si ottiene la connessione JDBC per un'origine dati XA, si tratta di una transazione JTA. Pertanto, se si esegue il codice in bean di sessione stateless, ad esempio con la transazione attivata, si ottengono automaticamente sia le entità che JDBC gestite da JTA.

EDIT Ecco un esempio di codice da Servlet

private @Resource DataSource xaDatasource; 
private @Resource UserTransaction utx; 
private @PersistenceUnit EntityManagerFactory factory; 

public void doGet(HttpServletRequest req, HttpServletResponse res) ... { 
    utx.begin(); 
    //Everything below this will be in JTA 
    Connection conn = xaDatasource.getConnection(); 
    EntityManager mgr = factory.createEntityManager(); 
    //Do your stuff 
    ... 
    utx.commit(); 
} 

responsabilità: codice non testato.

Proprio rendo conto che non è la primavera, ma lo lascerò fino comunque

Problemi correlati