Весна 3: объект сущности сохраняется даже после выброса исключения

#spring #hibernate #spring-mvc

#весна #впасть в спящий режим #spring-mvc #спящий режим

Вопрос:

транзакция была инициирована с использованием следующих кодов в файле контекста приложения:

 <tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="get*" read-only="true" />
        <tx:method name="*InOwnTransaction" propagation="REQUIRES_NEW" 
            rollback-for="com.dummy.common.exception.DummyException" />
        <tx:method name="*" propagation="REQUIRED"
            rollback-for="com.dummy.common.exception.DummyException" />
    </tx:attributes>
</tx:advice>
  

В Service impl функция для обновления объекта

 public Offering selectiveUpdateInOwnTransaction(Map<String, String> offeringData) throws DummyException
{
    // search data from DB
    AbstractJpaEntity priceObj = selectEntityByCriteria(Price.class, paramMap);
    // now "priceObj" contain requied data;
    ..
    priceObj.setPublishedStatus(true);
    // some businesslogic which is throwing exception   <-- Point 1 : This is throwing exception
    priceObj.setPrice(23);
    ..
    updateEntity(priceObj);
}
  

Две функции, вызывающие ту же вышеупомянутую функцию «selectiveUpdateInOwnTransaction»

 public boolean updateOffering(Offering offering) // In same Service Impl
public boolean updateOfferingInOwnTransaction(....) throws DummyException // In different Service Impl
  

В обеих этих функциях вызов «selectiveUpdateInOwnTransaction» выглядит следующим образом:

 try
{
    selectiveUpdateInOwnTransaction(dataMap);
}
catch (DummyException e)
{
    ....
}
  

Проблема:
Пункт 1 вызывает исключение, и «priceObj» не должен обновляться в БД, но когда вызывается «selectiveUpdateInOwnTransaction» из «updateOffering», он сохраняется в БД.

Также только содержимое, которое сохраняется в БД, — это содержимое, которое обновляется в объекте перед выбросом исключения.

Вызов из «updateOfferingInOwnTransaction» не показывает такой ошибки.
Я не могу понять, почему «updateOffering» не работает в соответствии с ожиданиями.

В качестве быстрого решения я внес следующие изменения в «selectiveUpdateInOwnTransaction», и после этого он работал нормально (объект не сохранялся в БД при выбросе исключения, как и ожидалось):

 Price priceObj1 = new Price();
BeanUtils.copyProperties(priceObj, priceObj1);
updateEntity(priceObj1);
  

Но и в этом я не понимаю, почему это работает?

Другие сведения о конфигурации

 <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<aop:config>
    <aop:pointcut id="serviceOperation"
            expression="execution(* com.dummy.offering.db.service..*Service.*(..)) || execution(* com.dummy.common.db.service..*Service.*(..))" />
    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice" />
</aop:config>
  

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

1. Можете ли вы опубликовать остальную часть конфигурации транзакции? Для каких классов применим этот совет?

2. Привет @AndreiStefan, я добавил другую (может быть) соответствующую конфигурацию в main post. контекст: component-scan содержит все pkg, которые содержат вышеупомянутые методы

3. Одна вещь: если вы полагаетесь на name="*InOwnTransaction" propagation="REQUIRES_NEW" то, что будете работать для вызова метода updateOffering , тогда этого не произойдет. Вы выполняете внутренний вызов (самостоятельный вызов) для проксируемого класса: внутренний вызов selectiveUpdateInOwnTransaction from updateOffering будет вызовом обычного (не проксируемого) метода. Предполагая, что ServiceImpl реализует интерфейс, который, возможно, вызывается Service , updateOffering указан ли метод в интерфейсе?

4. привет @AndreiStefan, updateOffering также записывается в интерфейсе. Эта функция вызывается из контроллера, который автоматически подключает этот интерфейс (сервис)

5. Я также попытался переименовать «updateOffering» в «updateOfferingInOwnTransaction», но ничего не изменил. Чего я не пробовал, так это добавления «throw DummyEcxeption» в объявление функции «updateOffering». (поскольку нет необходимости выбрасывать его из него)

Ответ №1:

Если вы полагаетесь на name="*InOwnTransaction" propagation="REQUIRES_NEW" то, что будете работать для вызова метода updateOffering , то этого не произойдет. Вы выполняете внутренний вызов (самостоятельный вызов) для проксируемого класса: внутренний вызов selectiveUpdateInOwnTransaction from updateOffering будет вызовом обычного (не проксируемого) метода.

Я настоятельно рекомендую внимательно прочитать этот раздел документации. Чтобы напрямую применить то, что у вас есть в вашем коде, к образцу в документации: SimplePojo ваш ServiceImpl , foo() ваш updateOffering и bar() ваш selectiveUpdateInOwnTransaction . Подумайте о прокси как о совершенно новом классе, который перехватывает вызовы ваших собственных методов и классов.

  • Итак, по сути, когда вы вызываете updateOffering со своего контроллера, вы вызываете updateOffering экземпляр другого класса (которого нет ServiceImpl ).
  • Этот новый класс применяет транзакционное поведение (запуск новой транзакции, связывание транзакционных ресурсов с текущим потоком и т. Д.), А Затем вызывает real updateOffering из вашего собственного ServiceImpl .
  • updateOffering затем вызывает selectiveUpdateInOwnTransaction , но поскольку этот вызов похож this.selectiveUpdateInOwnTransaction на then , вызов будет на вашем ServiceImpl , а не на вновь созданном классе, который действует как прокси. Из-за этого Spring обрабатывает ваш selectiveUpdateInOwnTransaction как обычный, ничего особенного.

С другой стороны, если selectiveUpdateInOwnTransaction вызывается из другого класса, этот другой класс вызовет этот метод на прокси-сервере, и именно поэтому он работает, если вы вызываете его из другого ServiceImpl .

В этом разделе документации есть уродливое решение этого ограничения

 public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}
  

но реальным приемлемым подходом было бы немного изменить дизайн ваших классов: перейти updateOffering в другой класс.

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

1. спасибо @Andrey. Хотя я не использую «AopContext». У меня появилась идея. Я повторно извлек компонент из контекста приложения и вызвал функцию selectiveUpdate, используя этот объект компонента. Он работает.

2. я хорошая ссылка для прокси-сервера forum.spring.io/forum/spring-projects/aop /. … Просто для информации для новичков, таких как я