Откат не работает для моего автономного приложения Spring

#spring #transactions #rollback

#spring #транзакции #Откат

Вопрос:

Я работаю над автономным приложением spring / jpa / hibernate.

Проблема, с которой я сталкиваюсь, заключается в том, что мое приложение не будет откатывать транзакции, даже если вызвано исключение RuntimeException.

Вот моя конфигурация:

  <context:annotation-config />
    <context:component-scan base-package="com.jeanbaptistemartin"/>
    <context:property-placeholder location="classpath:application.properties"/>
    <bean id="gestionnaireMailing" class="com.jeanbaptistemartin.desktop.JFrameGestionnaireMailing" init-method="init" >

    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
        <property name="shared" value="true"/>
    </bean>

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="${mail.server}"/>
        <property name="port" value="${mail.port}"/>
        <property name="javaMailProperties">
            <props>
                <prop key="mail.smtp.connectiontimeout">2000</prop>    
                <prop key="mail.smtp.timeout">2000</prop>    
            </props>    
        </property>
    </bean>
    <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
        <property name="velocityProperties">
            <value>
            resource.loader=class
            class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
            </value>
        </property>
    </bean>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="jbmPU" />
        <property name="persistenceXmlLocation" value="classpath:/META-INF/persistence.xml" />
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="${database.showSql}" />
                <property name="generateDdl" value="${database.generateDdl}"/>
                <property name="databasePlatform" value="${database.dialect}"/>
            </bean>
        </property>
    </bean>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${database.driver}"/>
        <property name="jdbcUrl" value="${database.url}"/>
        <property name="user" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="idleConnectionTestPeriod" value="3000" />
        <property name="loginTimeout" value="300" />
    </bean>
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager" />
  

Мой транзакционный метод:

  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {RuntimeException.class})
    public boolean mailAbonne(List<Sculpture> sculpturesChoisiesPourMailing, Abonne abonne) {
        try {
            for (Sculpture sculpture : sculpturesChoisiesPourMailing) {
                MailingAbonnePK mapk = new MailingAbonnePK(sculpture.getSculptureID(), abonne.getAbonneID());
                MailingAbonne ma = new MailingAbonne(mapk, new Date());
                dao.persistMailingAbonnee(ma);
            }
            envoyerMail(sculpturesChoisiesPourMailing, abonne);//this method sometimes throws a RuntimeException.
            return true;
        } catch (RuntimeException e) {
            log.error("Exception");
            log.error(e);
            throw new RuntimeException();
        }
    }
  

В моем dao:

 @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
  

Теперь несколько слов о текущем поведении моего приложения:

когда envoyerMail вызывает исключение RuntimeException или его подкласс, приложения просто зависают на неопределенный срок.

Теперь еще несколько слов о желаемом поведении моего приложения.

Мой метод mailAbonne вызывается в цикле следующим образом:

  for (Abonne abonne : totalAbonnes) {
   mailAbonne(sculpturesChoisiesPourMailing, abonne);
}
  

В идеале я хотел бы, чтобы одна итерация цикла завершилась неудачно или завершилась успешно атомарно, т. Е. если исключение RuntimeException возникает на 3-й итерации из общего числа 5 итераций, тогда у меня в базе данных были бы данные, соответствующие 4 успешным итерациям, и данные, соответствующие неудачной итерации, были бы откатаны.

Кто-нибудь может, пожалуйста, помочь?

J.

Ответ №1:

Прежде всего, вам не нужно указывать rollbackFor = {RuntimeException.class} . Поведение по умолчанию заключается в откате для любого исключения во время выполнения.

Похоже, ваша проблема заключается в том, что вы вызываете свой транзакционный метод из другого метода того же компонента. Spring запускает и останавливает транзакции автоматически, потому что он переносит каждый компонент внутрь прокси, который обрабатывает эту транзакцию. Когда вы вызываете метод из того же компонента, прокси-сервер не может перехватить вызов и запустить / остановить транзакцию для вас. Таким образом, вы должны поместить транзакционный метод в другой компонент.

Затем для вашей итерации. Для работы вам нужно

  • сделать метод, содержащий цикл, не транзакционным, чтобы каждый вызов mailAbonne запускал новую транзакцию
  • или для распространения метода mailAbonne требуется REQUIRES_NEW, чтобы у него была своя независимая транзакция

Конечно, вам также необходимо переносить каждый вызов mailAbonne в цикле внутри блока try / catch, чтобы перехватывались исключения во время выполнения и следующий вызов mailAbonne можно было выполнить, даже если текущий завершился неудачей.

Комментарии:

1. Большое спасибо JB Nizet! Весной я не знал об этом. Ваш совет устранил проблему. Еще раз спасибо, Жюльен.