简体   繁体   中英

@transactional annotated class wrapped with proxy, but transaction is not created

Well... Tried to find in google and here but failed. Here is my story:

  • Spring MVC 3.1.1 RELEASE
  • Spring Data JPA 1.1.0 RELEASE
  • Hibernate 3.6.9.Final

Problem: I have method save(...) with @Transactional(propagation=Propagation.REQUIRES_NEW) annotation. But transaction is not being created.

Other findings: 1) When I annotate with @Transactional method from other service, transaction being created.

21:49:13.397 [DEBUG] (http-8080-2) org.springframework.orm.jpa.JpaTransactionManager  - Creating new transaction with name [com.xen.components.page.PageServiceImpl.findAllOrdered]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''

2) Service where this method is located being wrapped with proxy(I saw in debug) and method being invoked from controller, so the call goes through proxy.
3) Both services in root application context. Output of log.error(context):

OrderServiceImpl  - Root WebApplicationContext: startup date [Tue Sep 17 21:48:30 FET 2013]; root of context hierarchy
PageServiceImpl  - Root WebApplicationContext: startup date [Tue Sep 17 21:48:30 FET 2013]; root of context hierarchy

Here is the code of method:

@Service
public class OrderServiceImpl extends CRUDServiceImpl<Order> implements OrderService {
private static final Logger log = Logger.getLogger(OrderServiceImpl.class);

@Autowired
private OrderRepository repo;

@Autowired
private OrderItemService orderItemService;

@Override
protected CRUDRepository<Order> getRepository() {
    return repo;
}

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public Order save(Order entity) {
    if (entity.getCreationDate() == null) {
        entity.setCreationDate(new Date());
    }

    if (entity.getId() == null) {
        increasePopularityForProductsInOrder(entity);
        // throws NoTransactionException: No transaction aspect-managed TransactionStatus in scope
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

        decreaseStockNumberForSkusInOrder(entity);
    }

    return super.save(entity);
}

private void increasePopularityForProductsInOrder(Order order) {
    List<OrderItem> items = order.getItems();

    for (OrderItem item : items) {
        orderItemService.increasePopularity(item);
    }
}

private void decreaseStockNumberForSkusInOrder(Order order) {
    List<OrderItem> items = order.getItems();

    for (OrderItem item : items) {
        orderItemService.removeFromStock(item);
    }
}
}

This method being invoked from controller. Controller code is straightforward, only validation and orderService.save(...) call. Here is my config:

applicationContext.xml

<beans>

<context:annotation-config />
<context:component-scan base-package="com.xen">
    <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>

<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="url" value="${database.url}" />
    <property name="driverClassName" value="${database.driverClassName}" />
    <property name="username" value="${database.username}" />
    <property name="password" value="${database.password}" /> 
</bean>

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

<tx:annotation-driven proxy-target-class="true"/>

 <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="persistenceUnitName" value="persistenceUnit"/>
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
</bean>

<jpa:repositories base-package="com.xen" />

</beans>

servletContext.xml

<context:annotation-config />
<context:component-scan base-package="com.xen" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>

<mvc:annotation-driven />

<mvc:resources location="/, classpath:/META-INF/web-resources/" mapping="/resources/**"/>

<mvc:default-servlet-handler/>

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.xen.common.util.PagePopulationInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

<bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource" p:basenames="WEB-INF/i18n/application" p:fallbackToSystemLocale="false"/>

<bean class="org.springframework.ui.context.support.ResourceBundleThemeSource" id="themeSource"/>

<bean class="org.springframework.web.servlet.theme.CookieThemeResolver" id="themeResolver" p:cookieName="theme" p:defaultThemeName="standard"/>

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" p:defaultErrorView="error">
    <property name="warnLogCategory" value="stdout" />
    <property name="exceptionMappings">
        <props>
            <prop key=".NoSuchRequestHandlingMethodException">404</prop>
            <prop key=".NotFoundException">404</prop>
        </props>
    </property>
</bean>

<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
  </bean>

<bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" id="tilesConfigurer">
    <property name="definitions">
          <list>
            <value>/WEB-INF/layouts/layouts.xml</value>
            <value>/WEB-INF/views/**/views.xml</value>
          </list>
    </property>
  </bean>

</beans>

persistance.xml

<persistence>
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
        <!-- value="create" to build a new database on each run; value="update" to modify an existing database; value="create-drop" means the same as "create" but also drops tables when Hibernate closes; value="validate" makes no changes to the database -->
        <property name="hibernate.hbm2ddl.auto" value="update" />
        <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
        <property name="hibernate.connection.charSet" value="UTF-8" />
    </properties>
</persistence-unit>
</persistence>

Update: CartController.java

@Controller
public class CartController {
    private static final Logger log = Logger.getLogger(CartController.class);

...

@RequestMapping(value = "/cart/submit-order", method = RequestMethod.POST)
    public String submit(@RequestParam String email, @RequestParam long deliveryType, Model uiModel, HttpServletRequest request, RedirectAttributes redirectAttrs) {
        Order order = CartHelper.getOrderFromRequest(request);
        uiModel.addAttribute("email", email);

        // 1. check if address is set
        if (StringUtil.isEmpty(order.getClientInfo().getFirstName())) {
            MessageBean.createErrorMessage("cart.enter-address.error").displayMessage(uiModel);
            populateForm(uiModel, order);
            return "cart";
        }

        // 2. check if specified deliveryType exist
        DeliveryType type = null;
        try {
            type = deliveryTypeService.findOne(deliveryType);
        } catch (NotFoundException nfe) {
            MessageBean.createErrorMessage("cart.invalid-delivery-type.error").displayMessage(uiModel);
            populateForm(uiModel, order);
            return "cart";
        }
        order.setDeliveryType(type);

        // 3. check if email is valid
        if (!StringUtil.isEmailValid(email)) {
            MessageBean.createErrorMessage("cart.invalid-email.error").displayMessage(uiModel);
            populateForm(uiModel, order);
            return "cart";
        }
        order.getClientInfo().setEmail(email);

        // 4. check if order is not empty
        if (order.isEmpty()) { // should not be possible
            log.warn("Empty order submission registered! " + order);
            return "cart/empty";
        }

        order = orderService.save(order);
        CartHelper.updateOrderForRequest(request, order);

        // 5. send notification to client
        try {
            sendNotificationToClient(order, request);
            redirectAttrs.addFlashAttribute(MessageBean.MESSAGE_BEAN_KEY, MessageBean.createSuccessMessage("cart.order.successfuly.created"));
        } catch (Exception e) {
            redirectAttrs.addFlashAttribute(MessageBean.MESSAGE_BEAN_KEY, MessageBean.createErrorMessage("cart.failed.to.send.notification.during.order.saving"));
        }

        return "redirect:/orders/success";
    }

...

}

If you have any thoughts, please, post it. I'm getting crazy about this.

Try specifying your transaction manger when initializing annotations, so update your config file to be like:

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM