我正在为我的JPA交易使用标准的JPA事务管理器。但是,现在我想添加一些将共享相同“数据源”的JDBC实体。如何使用spring事务使JDBC操作具有事务性?我是否需要切换到JTA事务管理器?是否可以同时使用JPA和具有相同数据源的JDBC事务服务?更好的是,是否可以混合这两笔交易?
更新: @Espen:
我有一个从SimpleJdbcDaoSupport扩展的dao,它使用getSimpleJDBCTemplate.update插入数据库行。当从服务代码抛出RuntimeException时,事务在使用JPATransactionManager时永远不会回滚。它在使用DatasourceTransactionManager时会回滚。我试图调试JPATransactionManager,似乎它永远不会对底层的JDBCConnection执行回滚(我想这是因为数据源不一定是JPA的JDBC)。我的配置设置与您在此处解释的完全相同。
以下是我的测试代码:
<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>
这里是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;
}
}
这是调用dao和throws异常(spring服务)的客户端代码
@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();
}
注意:使用DatasourceTransactionManager时,代码按预期工作
更新 - 2:
好的,我找到了问题的根本原因。感谢Espen。
我的实体经理配置是这样的(从春季宠物诊所应用程序复制):
<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="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>
现在一切似乎都在发挥作用!谁能解释这两种方法的区别?
答案 0 :(得分:26)
可以使用JpaTransactionManager
在同一事务中混合使用JPA和JDBC代码。
Spring 3 JavaDoc的片段:
此事务管理器也支持 直接在一个数据源内访问 事务(即纯JDBC代码 使用相同的DataSource)。 这允许混合服务 访问JPA和使用的服务 普通的JDBC(没有意识到 JPA)!
您应该知道,JPA会缓存查询并在事务结束时执行所有查询。因此,如果您希望使用JPA在事务中保留一些数据,然后使用JDBC检索数据,那么在尝试使用JDBC代码检索之前,如果没有明确地刷新JPA的持久性上下文,它将无法工作。
使用JDBC代码声明JPA代码在事务中删除了一行的代码示例:
@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);
}
如果您更喜欢使用JpaTemplate
课程,只需将entityManager.flush()
替换为jpaTemplate.flush();
回应Sajids的评论: 使用Spring,您可以配置支持JPA和JDBC的事务管理器,如下所示:
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Transaction manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa
.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
和Annotation-Driven版本
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf);
return jpaTransactionManager;
}
为了使其工作,必须使用JdbcTemplate或SimpleJdbcTemplate类执行JDBC查询。在使用扩展SimpleJdbcDaoSupport的DAO的情况下,您应该使用getSimpleJdbcTemplate(..)方法。
最后让两个DAO方法参与同一个事务,从@Transactional注释的服务类中调用两个DAO方法。使用配置中的<tx:annotation-driven>
元素,Spring将使用给定的事务管理器为您处理事务。
在业务层:
public class ServiceClass {..
@Transactional
public void updateDatabase(..) {
jpaDao.remove(..);
jdbcDao.insert(..);
}
}
编辑2: 然后出了点问题。它完全按照Javadoc中的规定对我有效。 您的实体管理器是否具有类似我的bean的数据源属性?只有在将相同的数据源注入实体管理器和扩展的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>
答案 1 :(得分:0)
我还没有详细解决这个问题,因为我没有混合使用JDBC和JPA,但是如果你获得了XA数据源的JDBC连接,那么它们就是JTA事务。因此,如果您在Stateless会话bean中运行代码,例如启用了事务,那么您将自动获得由JTA管理的实体和JDBC。
修改强>
以下是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();
}
免责声明:未对代码进行测试。
只是意识到这不是春天,但无论如何我都会把它留下来