【问题标题】:What transaction manager should I use for JBDC template When using JPA ?使用 JPA 时,我应该为 JBDC 模板使用什么事务管理器?
【发布时间】:2011-02-10 01:09:26
【问题描述】:

我正在为我的 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并抛出异常的客户端代码(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:

好的,我找到了问题的根本原因。感谢埃斯彭。

我的实体管理器配置是这样的(从 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>

然后我改成这样:

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

现在一切似乎都正常了!谁能解释这两种方法之间的区别?

【问题讨论】:

  • 尝试删除 persistenceXmlLocation 属性。它是 dataSource 属性的替代方案。 JpaTransactionManager 与 JPA 和 JDBC 查询一起工作的要求是您的 entityManager 使用与 JDBC 查询相同的数据源,并且您指定 JPA 方言,就像您已经完成的那样。

标签: java spring jpa jdbc


【解决方案1】:

可以使用JpaTransactionManager 在同一事务中混合 JPA 和 JDBC 代码。

来自 Spring 3 的JavaDocsn-p:

此事务管理器还支持 直接 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 的事务管理器:

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

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

和注释驱动的版本

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

为了使其工作,必须使用 JdbcTemplate 或 SimpleJdbcTemplate 类执行 JDBC 查询。对于扩展 SimpleJdbcDaoSupport 的 DAO,您应该使用 getSimpleJdbcTemplate(..) 方法。

最后要让两个 DAO 方法参与同一个事务,请从使用 @Transactional 注释的服务类方法中调用这两个 DAO 方法。通过配置中的&lt;tx:annotation-driven&gt; 元素,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>

【讨论】:

  • @Espen Thaks 的答案。但是,我对独立的 JDBC Dao 实现更感兴趣。例如,我有 UserDaoJPAImpl for User 和 OrderDaoJDBCImpl for Order。 OrderDaoJDBCImpl 扩展了“SimpleJdbcDaoSupport”。我的第一个问题,如何使 OrderDaoJDBCImpl 事务性(声明性注释或 xml)。我是否必须编写另一个事务管理器?我的第二个问题。我如何混合这两个交易?我可以在 EJB 2.1 CMT 与 BMT 事务(使用 JTA)中混合使用。但是有了 Spring & JPA ,没有 JTA 有可能吗?
  • @Sajid:声明式事务在 Spring 和 EJB 3 中的工作方式几乎相同。您使用 @Transactional 代替 @TransactionAttribute。是的,如果没有 JTA 和 Spring,它绝对是可能的。它只要求所有查询都针对同一个数据源执行。一个
  • 我有一个从 SimpleJdbcDaoSupport 扩展的 dao,它使用 getSimpleJDBCTemplate.update 插入数据库行。当服务代码抛出 RuntimeException 时,使用 JPATransactionManager 时事务永远不会回滚。使用 DatasourceTransactionManager 时会回滚。我试图调试 JPATransactionManager 并且似乎它永远不会对底层 JDBCConnection 执行回滚(我猜是因为数据源不一定必须是 JPA 的 JDBC)。我的配置设置与您在此处解释的完全一样。
  • 是的,LocalContainerEntityManagerFactoryBean 被注入数据源。你能给我指出一个工作代码,以便我可以解决这些差异吗?我在 spring 源代码中看到的所有示例应用程序都没有 JPA/JDBC 组合。
【解决方案2】:

我还没有真正详细地解决这个问题,因为我没有混合使用 JDBC 和 JPA,但是如果你为 XA 数据源获取 JDBC 连接,那么它们就是 JTA 事务。因此,如果您在无状态会话 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();
}

免责声明:代码未经测试。

意识到这不是春天,但无论如何我都会放弃它

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-22
    • 2016-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多