2010-04-20 11 views
22

Estoy usando el administrador de transacciones estándar JPA para mis transacciones JPA. Sin embargo, ahora quiero agregar algunas entidades JDBC que compartirán el mismo 'origen de datos'. ¿Cómo puedo hacer que las operaciones JDBC sean transaccionales con la transacción de primavera? ¿Debo cambiar a los administradores de transacciones de JTA? ¿Es posible utilizar el servicio transaccional JDBC JPA & con el mismo origen de datos? Mejor aún, ¿es posible mezclar estas dos transacciones?¿Qué gestor de transacciones debería usar para la plantilla JBDC cuando uso JPA?

ACTUALIZACIÓN: @Espen:

Tengo un DAO se extendía desde SimpleJdbcDaoSupport que utiliza getSimpleJDBCTemplate.update para insertar una fila de base de datos. Cuando se arroja una RuntimeException desde el código de servicio, la transacción nunca retrocede cuando se utiliza JPATransactionManager. Se retrotrae al usar DatasourceTransactionManager. Traté de depurar el JPATransactionManager y parece que nunca realiza una reversión en JDBCConnection subyacente (supongo que debido al hecho de que el origen de datos no necesariamente tiene que ser JDBC para JPA). Mi configuración de configuración es exactamente como la que explicaste aquí.

Éstos son mis códigos de prueba:

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

y aquí está la 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; 
} 

} 

Aquí es el código de cliente que llama a la DAO y lanza una excepción (servicio de 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: el código funciona como se esperaba cuando se utiliza DataSourceTransactionManager

ACTUALIZACIÓN - 2:

Ok he encontrado la causa raíz del problema. Gracias a Espen.

Mi configuración del gestor de entidad era así (copiado de la primavera de aplicación mascota-clínica):

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

Entonces me cambiaron a este aspecto:

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

Ahora todo parece estar funcionando! ¿Alguien puede explicar la diferencia entre estos dos enfoques?

+0

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

+0

Trate de eliminar la propiedad persistenceXmlLocation. Es una alternativa a la propiedad dataSource. Los requisitos para JpaTransactionManager para trabajar con consultas JPA y JDBC es que su entityManager usa el mismo DataSource que sus consultas JDBC y que usted especifica el dialecto JPA como ya lo ha hecho. – Espen

Respuesta

25

Es posible mezclar código de APP y JDBC en la misma transacción utilizando la JpaTransactionManager.

un fragmento de resorte 3 de JavaDoc:

Este gestor de transacciones también soporta acceso DataSource directa dentro de una transacción (es decir llanura código JDBC trabajando con la misma fuente de datos). Esto permite mezclar servicios que acceden a JPA y servicios que usan JDBC simple (sin tener conocimiento de JPA)!

Sin embargo, debe tener en cuenta que JPA almacena en caché las consultas y las ejecuta al final de una transacción. Entonces, si desea conservar algunos datos dentro de una transacción con JPA y luego recuperar los datos con JDBC, no funcionará sin enjuagar explícitamente el contexto de persistencia de la JPA antes de intentar recuperarlo con el código JDBC.

Un ejemplo de código que afirma con código JDBC que el código JPA elimina una fila dentro de una transacción:

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

Y si usted prefiere utilizar la clase JpaTemplate, basta con sustituir el entityManager.flush() con jpaTemplate.flush();

En respuesta al comentario de Sajids: Con Spring, puede configurar un administrador de transacciones que admita tanto JPA como JDBC como este:

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

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

y la versión

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

Con el fin de hacer que funcione Anotación-Driven, las consultas JDBC debe ser ejecutado con los JdbcTemplate o la clase SimpleJdbcTemplate. En su caso con el DAO que extiende SimpleJdbcDaoSupport, debe usar el método getSimpleJdbcTemplate (..).

Y, finalmente, para permitir que dos métodos DAO participen en la misma transacción, llame a ambos métodos DAO de una clase de servicio metho anotada con @Transactional. Con el elemento <tx:annotation-driven> en su configuración, Spring manejará la transacción por usted con el administrador de transacciones determinado.

En la capa de negocio:

public class ServiceClass {.. 

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

Edición 2: entonces algo está mal. Funciona para mí exactamente como se especifica en el Javadoc. ¿Su administrador de entidades tiene una propiedad de fuente de datos como mi bean a continuación? Solo funcionará mientras usted esté inyectando la misma fuente de datos en el administrador de entidades y sus clases extendidas de 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 Thaks por la respuesta. Sin embargo, estoy más interesado en la implementación independiente de JDBC Dao. Por ejemplo, tengo UserDaoJPAImpl for User y OrderDaoJDBCImpl for Order. OrderDaoJDBCImpl extiende 'SimpleJdbcDaoSupport'. mi primer problema, ¿cómo puedo hacer OrderDaoJDBCImpl transaccional (declarativa, ya sea anotación o xml). ¿Debo escribir otro administrador de transacciones? mi segundo problema. ¿Cómo mezclo estas dos transacciones? Podría hacer esta mezcla en EJB 2.1 CMT vs transacciones BMT (usa JTA). Pero con Spring & JPA, ¿es posible sin JTA? – Sajid

+0

@Sajid: las transacciones declarativas funcionan casi igual con Spring que con EJB 3. En su lugar, @TransactionAttribute utiliza @Transactional. Y sí, definitivamente es posible sin JTA y con Spring. Solo requiere que todas las consultas se ejecuten contra el mismo origen de datos. A – Espen

+0

Tengo un dao extendido desde SimpleJdbcDaoSupport que usa getSimpleJDBCTemplate.update para insertar una fila de base de datos. Cuando se arroja una RuntimeException desde el código de servicio, la transacción nunca retrocede cuando se utiliza JPATransactionManager. Se retrotrae al usar DatasourceTransactionManager. Intenté depurar el JPATransactionManager y parece que nunca realiza una reversión en JDBCConnection subyacente (supongo que debido al hecho de que el origen de datos no necesariamente tiene que ser JDBC para JPA). Mi configuración de configuración es exactamente como la que explicaste aquí. – Sajid

0

Realmente no he resuelto esto en detalle ya que no he mezclado tanto JDBC como JPA, pero si obtiene su conexión JDBC para un origen de datos XA, entonces son transacciones JTA. Por lo tanto, si ejecuta su código en bean de sesión sin estado, por ejemplo, con la transacción activada, automáticamente obtendrá sus entidades y JDBC administrados por JTA.

EDITAR Aquí es un ejemplo de código de 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(); 
} 

de responsabilidad: Código no probado.

sólo se dan cuenta que esto no es la primavera, pero lo voy a dejar de todos modos

Cuestiones relacionadas