transaction transacciones ejemplo con java spring jpa jdbc

java - ejemplo - transacciones con jdbctemplate



¿Qué gestor de transacciones debería usar para la plantilla JBDC cuando uso JPA? (2)

Es posible mezclar código JPA y JDBC en la misma transacción usando JpaTransactionManager .

Un fragmento del JavaDoc de Spring 3:

Este administrador de transacciones también admite el acceso directo a DataSource dentro de una transacción (es decir, código JDBC simple que funciona con el mismo DataSource). 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 eliminó 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 prefiere usar la clase JpaTemplate , simplemente reemplace la entityManager.flush() con jpaTemplate.flush();

En respuesta al comentario de Sajids: con Spring puedes configurar un administrador de transacciones que admita tanto JPA como JDBC de la siguiente manera:

<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 impulsada por anotaciones

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

Para que funcione, las consultas JDBC deben ejecutarse con 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 desde un metodo de clase de servicio anotado con @Transactional. Con el elemento <tx:annotation-driven> en su configuración, Spring manejará la transacción por usted con el administrador de transacciones dado.

En la capa de negocios:

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

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

Estoy usando el administrador de transacciones JPA estándar 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 JPA y JDBC con el mismo origen de datos? Mejor aún, ¿es posible mezclar estas dos transacciones?

ACTUALIZACIÓN: @Espen:

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

Aquí están 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Á EL 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í está el código del cliente que llama al dao y lanza la 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 al usar DatasourceTransactionManager

ACTUALIZACIÓN - 2:

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

La configuración de mi administrador de entidades fue así (copiada de la aplicación de clínica de mascotas de primavera):

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

Luego lo cambié a esto así:

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


Realmente no he resuelto esto en detalle ya que no he mezclado JDBC y 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í hay un código de ejemplo 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(); }

Descargo de responsabilidad: Código no probado.

Solo date cuenta de que esto no es primavera, pero lo dejo de todos modos