Подтвердить что ты не робот

Какой менеджер транзакций следует использовать для шаблона JBDC При использовании JPA?

Я использую стандартный менеджер транзакций JPA для своих транзакций JPA. Однако теперь я хочу добавить некоторые объекты JDBC, которые будут использовать один и тот же "источник данных". Как я могу сделать транзакции JDBC транзакцией с транзакцией spring? Нужно ли мне переключаться на менеджеров транзакций JTA? Можно ли использовать транзакционную службу JPA и JDBC с одним и тем же источником данных? Еще лучше, можно ли смешивать эти две транзакции?

UPDATE: @Espen:

У меня есть dao, расширенный от SimpleJdbcDaoSupport, который использует getSimpleJDBCTemplate.update для вставки строки базы данных. Когда RuntimeException выбрасывается из служебного кода, транзакция никогда не откатывается при использовании JPATransactionManager. Он откатывается при использовании DatasourceTransactionManager. Я попытался отладить JPATransactionManager и кажется, что он никогда не выполняет откаты по базовому JDBCConnection (я думаю, из-за того, что источник данных не обязательно должен быть JDBC для JPA). Моя настройка конфигурации точно так же, как вы объяснили здесь.

Вот мои тестовые коды:

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

И ЗДЕСЬ ДАО:

@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 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();
}

ПРИМЕЧАНИЕ: код работает как ожидалось при использовании 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>

Теперь все работает! Может ли кто-нибудь объяснить разницу между этими двумя подходами?

4b9b3361

Ответ 1

Возможно сопоставить JPA и JDBC-код в той же транзакции, используя JpaTransactionManager.

Отрывок из Spring 3 JavaDoc:

Этот менеджер транзакций также поддерживает прямой доступ к данным в транзакция (т.е. простой JDBC-код работая с одним и тем же DataSource). Это позволяет смешивать услуги, которые доступ к JPA и услуги, которые используют простой JDBC (не зная о JPA)!

Вы должны знать, что JPA кэширует запросы и выполняет их все в конце транзакции. Поэтому, если вы хотите сохранить некоторые данные внутри транзакции с помощью JPA, а затем получить данные с помощью JDBC, она не будет работать без явного удаления контекста сохранения JPA, прежде чем вы попытаетесь восстановить его с помощью кода JDBC.

Пример кода, который утверждает с кодом 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;
}

Чтобы заставить его работать, запросы JDBC должны выполняться с помощью класса JdbcTemplate или SimpleJdbcTemplate. В вашем случае с DAO, который расширяет SimpleJdbcDaoSupport, вы должны использовать метод getSimpleJdbcTemplate (..).

И, наконец, чтобы два метода DAO участвовали в одной транзакции, вызовите оба метода DAO из класса сервиса metho, аннотированного с помощью @Transactional. С элементом <tx:annotation-driven> в вашей конфигурации Spring будет обрабатывать транзакцию для вас с данным менеджером транзакций.

На бизнес-уровне:

public class ServiceClass {..

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

Изменить 2: Тогда что-то не так. Он работает для меня точно так, как указано в Javadoc. У вашего менеджера объектов есть свойство datasource, например my 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>

Ответ 2

Я не очень подробно разбирался в этом вопросе, так как я не смешивал JDBC и JPA, но если вы получаете соединение JDBC для источника данных XA, то это транзакция JTA. Поэтому, если вы запускаете свой код в сеансе без состояния bean, например, при включенной транзакции, вы автоматически получаете как свои объекты, так и JDBC, управляемые JTA.

ИЗМЕНИТЬ Вот пример кода из 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, но я все равно оставлю его