我应该将什么事务管理器用于JBDC模板使用JPA时?
我正在为JPA事务使用标准的JPA事务管理器。 但是,现在我想添加一些将共享相同“数据源”的JDBC实体。 如何使用spring事务使JDBC操作具有事务性? 我是否需要切换到JTA事务管理器? 是否可以将JPA和JDBC事务服务与相同的数据源一起使用? 更好的是,是否可以混合这两笔交易?
更新:@Espen:
我有一个从SimpleJdbcDaoSupport扩展的dao,它使用getSimpleJDBCTemplate.update插入数据库行。 当从服务代码抛出RuntimeException时,事务在使用JPATransactionManager时永远不会回滚。 它在使用DatasourceTransactionManager时会回滚。 我试图调试JPATransactionManager,似乎它永远不会在底层JDBCConnection上执行回滚(我想由于数据源不一定是JPA的JDBC)。 我的配置设置与您在此处解释的完全相同。
这是我的测试代码:
<!-- --> <!-- -->
这里是道:
@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并抛出exception的客户端代码(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。
我的实体管理器配置是这样的(从春季宠物诊所应用程序复制):
然后我把它改成这样:
现在一切似乎都在起作用! 谁能解释这两种方法之间的区别?
可以使用JpaTransactionManager
在同一事务中混合JPA和JDBC代码。
来自Spring 3的JavaDoc的片段:
此事务管理器还支持在事务中直接访问DataSource(即使用相同DataSource的纯JDBC代码)。 这允许混合访问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的事务管理器,如下所示:
和注释驱动版本
@Bean public JpaTransactionManager transactionManager(EntityManagerFactory emf) { JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); jpaTransactionManager.setEntityManagerFactory(emf); return jpaTransactionManager; }
为了使其工作,必须使用JdbcTemplate或SimpleJdbcTemplate类执行JDBC查询。 在使用扩展SimpleJdbcDaoSupport的DAO的情况下,您应该使用getSimpleJdbcTemplate(..)方法。
最后让两个DAO方法参与同一个事务,从@Transactional注释的服务类中调用两个DAO方法。 使用配置中的
元素,Spring将使用给定的事务管理器为您处理事务。
在业务层:
public class ServiceClass {.. @Transactional public void updateDatabase(..) { jpaDao.remove(..); jdbcDao.insert(..); } }
编辑2:然后出了点问题。 它完全按照Javadoc中的规定对我有用。 您的实体管理器是否具有类似我的bean的数据源属性? 只有在将相同的数据源注入实体管理器和扩展的JpaDaoSupport类时,它才会起作用。
hibernate.format_sql=true
我还没有详细解决这个问题,因为我没有混合使用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(); }
免责声明:代码未经测试。
只是意识到这不是spring,但无论如何我都会把它留下来