2010-04-20 4 views
22

Ich verwende Standard-JPA-Transaktionsmanager für meine JPA-Transaktionen. Jetzt möchte ich jedoch einige JDBC-Entitäten hinzufügen, die die gleiche 'Datenquelle' teilen. Wie kann ich die JDBC-Vorgänge mit der Transaktion im Frühling transaktionsfähig machen? Muss ich zu JTA Transaktionsmanagern wechseln? Ist es möglich, beide JPA & JDBC Transaktionsservice mit derselben Datenquelle zu verwenden? Noch besser, ist es möglich, diese beiden Transaktionen zu mischen?Welchen Transaktionsmanager sollte ich für die JBDC-Vorlage verwenden, wenn JPA verwendet wird?

UPDATE: @Espen:

Ich habe ein dao von SimpleJdbcDaoSupport erweitert, die getSimpleJDBCTemplate.update eine Datenbank Zeile einzufügen verwendet. Wenn eine RuntimeException vom Dienstcode ausgelöst wird, wird die Transaktion bei Verwendung von JPATransactionManager nie zurückgesetzt. Bei Verwendung von DatasourceTransactionManager wird ein Rollback ausgeführt. Ich habe versucht, den JPATransactionManager zu debuggen und scheint, dass es Rollback auf zugrundeliegendem JDBCConnection nie durchführt (ich denke, aufgrund der Tatsache, dass die Datenquelle nicht notwendigerweise JDBC für JPA sein muss). Meine Konfigurationseinstellungen sind genau so, wie Sie es hier erklärt haben.

Hier sind meine Testcode:

<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> 

und hier ist die 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; 
} 

} 

Hier ist der Client-Code, der die dao nennt und wirft Ausnahme (Frühjahr Dienst)

@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(); 
} 

HINWEIS: der Code funktioniert wie erwartet, wenn DatasourceTransactionManager

UPDATE - 2:

Ok Ich habe die Ursache des Problems gefunden. Danke an Espen.

Konfiguration Meine Einheit Manager war wie folgt (von Frühling Haustier-Klinik App kopiert):

<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> 

Dann habe ich es änderte sich dies mögen:

<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> 

Jetzt ist alles scheint zu funktionieren! Kann jemand den Unterschied zwischen diesen beiden Ansätzen erklären?

+0

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

+0

Versuchen Sie, die persistenceXmlLocation Eigenschaft zu entfernen. Es ist eine Alternative zur Eigenschaft dataSource. Die Anforderungen, damit der JpaTransactionManager sowohl mit JPA- als auch JDBC-Abfragen arbeiten kann, ist, dass Ihr entityManager die gleiche dataSource wie Ihre JDBC-Abfragen verwendet und Sie den JPA-Dialekt wie bereits beschrieben angeben. – Espen

Antwort

25

Es ist möglich, JPA und JDBC-Code in derselben Transaktion zu mischen, um die JpaTransactionManager verwenden.

Ein Ausschnitt aus Spring 3s JavaDoc:

Dieser Transaktions-Manager unterstützt auch direkten Datasource-Zugriff innerhalb einer Transaktion (das heißt schlicht JDBC-Code mit derselben Datasource arbeiten). Dies ermöglicht Mischdienste, die Zugang JPA und Dienste, die plain JDBC verwenden (ohne Kenntnis von JPA)!

Sie sollten jedoch beachten, dass JPA die Abfragen zwischenspeichert und alle am Ende einer Transaktion ausführt. Wenn Sie also einige Daten innerhalb einer Transaktion mit JPA beibehalten und dann die Daten mit JDBC abrufen möchten, funktioniert dies nicht, ohne den JPA-Persistenzkontext explizit zu löschen, bevor Sie versuchen, ihn mit JDBC-Code erneut zu öffnen.

Ein Codebeispiel, das mit JDBC-Code behauptet, dass die PPV-Code eine Zeile innerhalb einer Transaktion gelöscht:

@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); 
} 

Und wenn Sie es vorziehen, verwenden, um die JpaTemplate Klasse, ersetzen Sie einfach die entityManager.flush() mit jpaTemplate.flush();

Als Reaktion auf den Kommentar von Sajids: Mit Spring können Sie einen Transaktionsmanager konfigurieren, der sowohl JPA als auch JDBC unterstützt:

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

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

und Annotation-Driven Version

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

Um es Arbeit zu machen, müssen die JDBC Abfragen mit der JdbcTemplate oder der SimpleJdbcTemplate Klasse ausgeführt werden. In Ihrem Fall mit dem DAO, das den SimpleJdbcDaoSupport erweitert, sollten Sie die Methode getSimpleJdbcTemplate (..) verwenden.

Und schließlich zwei DAO-Methoden in derselben Transaktion teilnehmen lassen, rufen Sie beide DAO-Methoden aus einer Service-Klasse-Methode, die mit @Transactional gekennzeichnet ist. Mit dem Element <tx:annotation-driven> in Ihrer Konfiguration wird Spring die Transaktion für Sie mit dem angegebenen Transaktionsmanager bearbeiten.

Auf der Business-Schicht:

public class ServiceClass {.. 

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

Edit 2: Dann ist etwas nicht in Ordnung. Es funktioniert genau wie im Javadoc angegeben. Hat Ihr Entity Manager eine Datenquelleneigenschaft wie meine Bean unten? Es funktioniert nur so lange, wie Sie dieselbe Datenquelle in den Entity Manager und Ihre erweiterten JpaDaoSupport-Klassen injizieren.

<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 Thaks für die Antwort. Ich bin jedoch mehr an der eigenständigen JDBC Dao-Implementierung interessiert. Zum Beispiel habe ich UserDaoJPAImpl für User und OrderDaoJDBCImpl für Order. OrderDaoJDBCImpl erweitert 'SimpleJdbcDaoSupport'. mein erstes Problem, wie mache ich OrderDaoJDBCImpl transaktional (deklarative entweder Annotation oder XML). Muss ich einen anderen Transaktionsmanager schreiben? mein zweites Problem. Wie mische ich diese beiden Transaktionen? Ich könnte dies in EJB 2.1 CMT vs BMT-Transaktionen mischen (verwendet JTA). Aber mit Spring & JPA, ist es möglich ohne JTA? – Sajid

+0

@Sajid: Deklarative Transaktionen funktionieren fast identisch mit Spring wie mit EJB 3. Statt @TransactionAttribute verwenden Sie stattdessen @Transactional. Und ja, es ist definitiv möglich ohne JTA und mit Spring. Es erfordert nur, dass alle Abfragen für dieselbe Datenquelle ausgeführt werden. A – Espen

+0

Ich habe einen Dao von SimpleJdbcDaoSupport erweitert, der getSimpleJDBCTemplate.update verwendet, um eine Datenbankzeile einzufügen. Wenn eine RuntimeException vom Dienstcode ausgelöst wird, wird die Transaktion bei Verwendung von JPATransactionManager nie zurückgesetzt. Bei Verwendung von DatasourceTransactionManager wird ein Rollback ausgeführt. Ich habe versucht, den JPATransactionManager zu debuggen und scheint, dass Rollback nie auf zugrunde liegenden JDBCConnection führt (ich denke, aufgrund der Tatsache, dass die Datenquelle nicht unbedingt JDBC für JPA sein muss). Meine Konfigurationseinstellungen sind genau so, wie Sie es hier erklärt haben. – Sajid

0

Ich habe das noch nicht im Detail ausgearbeitet, da ich JDBC und JPA nicht gemischt habe, aber wenn Sie Ihre JDBC-Verbindung für eine XA-Datenquelle bekommen, dann handelt es sich um JTA-Transaktionen. Wenn Sie Ihren Code beispielsweise in Stateless Session Bean ausführen und die Transaktion aktiviert ist, erhalten Sie automatisch Ihre Entitäten und JDBC, die von JTA verwaltet werden.

EDIT Hier ist ein Beispiel-Code aus 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(); 
} 

Haftungsausschluss: Code nicht getestet.

realisieren Gerade dies nicht ist der Frühling, aber ich werde es trotzdem überlassen

Verwandte Themen