Какой менеджер транзакций я должен использовать для шаблона JBDC При использовании JPA? - PullRequest
22 голосов
/ 20 апреля 2010

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

UPDATE: @Espen:

У меня есть дао, расширенный от 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 и выдает исключение (служба 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>

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

Ответы [ 2 ]

26 голосов
/ 20 апреля 2010

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

Фрагмент из Spring 3's JavaDoc :

Этот менеджер транзакций также поддерживает прямой доступ к источникам данных в пределах транзакция (то есть простой код JDBC работая с тем же источником данных). Это позволяет смешивать услуги, которые доступ к 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();

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

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

public class ServiceClass {..

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

Редактировать 2: Тогда что-то не так. Это работает для меня точно так, как указано в Javadoc. Есть ли у вашего менеджера сущностей свойство источника данных, подобное моему бину ниже? Он будет работать только до тех пор, пока вы внедряете один и тот же источник данных в менеджер сущностей и в свои расширенные классы 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>
0 голосов
/ 20 апреля 2010

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

EDIT Вот пример кода из 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();
}

Отказ от ответственности: код не проверен.

Просто поймите, что это не Весна, но я все равно оставлю это

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...