Java 使用 JPA 时,我应该为 JBDC 模板使用什么事务管理器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2673678/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What transaction manager should I use for JBDC template When using JPA ?
提问by Sajid
I am using standard JPA transaction manager for my JPA transactions. However, now I want to add some JDBC entities which will share the same 'datasource'. How can I make the JDBC operations transactional with spring transaction? Do I need to switch to JTA transaction managers? Is it possible to use both JPA & JDBC transactional service with same datasource? Even better, is it possible to mix these two transactions?
我正在为我的 JPA 事务使用标准 JPA 事务管理器。但是,现在我想添加一些共享相同“数据源”的 JDBC 实体。如何使用 spring 事务使 JDBC 操作具有事务性?我是否需要切换到 JTA 事务管理器?是否可以对相同的数据源同时使用 JPA 和 JDBC 事务服务?更好的是,是否可以混合这两种交易?
UPDATE: @Espen :
更新:@埃斯彭:
I have a dao extended from SimpleJdbcDaoSupport which uses getSimpleJDBCTemplate.update to insert a database row. When a RuntimeException is thrown from the service code, the transaction never rolls back when using JPATransactionManager. It does rollback when using DatasourceTransactionManager. I tried to debug the JPATransactionManager and seems that it never performs rollback on underlying JDBCConnection(I guess due to the fact that the datasource is not necessarily has to be JDBC for JPA). My configuration setup are exactly like you explained here.
我有一个从 SimpleJdbcDaoSupport 扩展的 dao,它使用 getSimpleJDBCTemplate.update 插入数据库行。当服务代码抛出 RuntimeException 时,事务在使用 JPATransactionManager 时永远不会回滚。它在使用 DatasourceTransactionManager 时会回滚。我试图调试 JPATransactionManager 并且似乎它从未对底层 JDBCConnection 执行回滚(我猜是因为数据源不一定是 JPA 的 JDBC)。我的配置设置与您在此处解释的完全一样。
Here are my test codes:
这是我的测试代码:
<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>
AND HERE IS THE DAO:
这是 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;
}
}
Here is the client code that calls the dao and throws exception (spring service)
下面是调用dao并抛出异常的客户端代码(spring service)
@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();
}
NOTE: the code works as expected when using DatasourceTransactionManager
注意:代码在使用 DatasourceTransactionManager 时按预期工作
UPDATE - 2:
更新 - 2:
Ok I have found the root cause of the problem. Thanks to Espen.
好的,我已经找到了问题的根本原因。感谢埃斯彭。
My entity manager configuration was like this(copied from spring pet-clinic app):
我的实体管理器配置是这样的(从 spring pet-clinic 应用程序复制):
<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>
Then I changed it to like this:
然后我把它改成这样:
<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>
Now everything seems to be working! Can anyone explain the difference between these two approach ?
现在一切似乎都在工作!谁能解释这两种方法之间的区别?
采纳答案by Espen
It's possible to mix JPA and JDBC code in the same transaction using the JpaTransactionManager
.
可以使用JpaTransactionManager
.
A snippet from Spring 3's JavaDoc:
Spring 3 的JavaDoc 的一个片段:
This transaction manager also supports direct DataSource access within a transaction (i.e. plain JDBC code working with the same DataSource). This allows for mixing services which access JPA and services which use plain JDBC (without being aware of JPA)!
该事务管理器还支持事务内的直接数据源访问(即使用相同数据源的普通 JDBC 代码)。这允许混合访问 JPA 的服务和使用普通 JDBC 的服务(不知道 JPA)!
You should be aware though that JPA caches the queries and executes all of them at the end of a transaction. So if you want to persist some data inside a transaction with JPA and then retrieve the data with JDBC, it will not work without explicitely flushing the JPA's persistence context before you attempt to retreive it with JDBC code.
您应该知道 JPA 会缓存查询并在事务结束时执行所有查询。因此,如果您想使用 JPA 在事务中保留一些数据,然后使用 JDBC 检索数据,那么在您尝试使用 JDBC 代码检索它之前,如果不明确刷新 JPA 的持久性上下文,它将无法工作。
A code example that asserts with JDBC code that the JPA code deleted a row inside a transaction:
使用 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);
}
And if you prefer to use the JpaTemplate
class, just replace the entityManager.flush()
with jpaTemplate.flush();
如果你喜欢使用JpaTemplate
类,只需更换entityManager.flush()
与jpaTemplate.flush();
In response to Sajids' comment: With Spring you can configure a transaction manager that supports both JPA and JDBC like this:
回应 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>
and Annotation-Driven version
和注释驱动版本
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf);
return jpaTransactionManager;
}
In order to make it work, the JDBC queries must be executed with the JdbcTemplate or the SimpleJdbcTemplate class. In your case with the DAO that extends the SimpleJdbcDaoSupport, you should use the getSimpleJdbcTemplate(..) method.
为了使其工作,必须使用 JdbcTemplate 或 SimpleJdbcTemplate 类执行 JDBC 查询。对于扩展 SimpleJdbcDaoSupport 的 DAO,您应该使用 getSimpleJdbcTemplate(..) 方法。
And finally to let two DAO methods participate in the same transaction, call both DAO methods from a service class metho annotated with @Transactional. With the <tx:annotation-driven>
element in your config, Spring will handle the transaction for you with the given transaction manager.
最后,为了让两个 DAO 方法参与同一个事务,从用 @Transactional 注释的服务类方法调用这两个 DAO 方法。使用<tx:annotation-driven>
配置中的元素,Spring 将使用给定的事务管理器为您处理事务。
On the business layer:
在业务层:
public class ServiceClass {..
@Transactional
public void updateDatabase(..) {
jpaDao.remove(..);
jdbcDao.insert(..);
}
}
Edit 2: Then something is wrong. It works for me exactly as specified in the Javadoc. Does your entity manager has a datasource property like my bean below? It will only work as long you're injecting the same datasource into the entity manager and your extended JpaDaoSupport classes.
编辑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>
回答by Chuk Lee
I've not really worked this out in detail yet as I've not mixed both JDBC and JPA but if you get your JDBC connection for an XA datasource then they are JTA transaction. So if you run your code in Stateless session bean for example with transaction turned on, then you automatically get both your Entities and JDBC managed by JTA.
我还没有真正详细地解决这个问题,因为我没有混合使用 JDBC 和 JPA,但是如果您获得 XA 数据源的 JDBC 连接,那么它们就是 JTA 事务。因此,如果您在无状态会话 bean 中运行代码,例如打开事务,那么您将自动获得 JTA 管理的实体和 JDBC。
EDITHere is an example code from Servlet
编辑这是来自的示例代码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();
}
Disclaimer: Code not tested.
免责声明:代码未经测试。
Just realize this is not Spring but I'll leave it up anyway
只是意识到这不是春天,但无论如何我都会离开它