0

I have a utility service called NotificationService. It follows along the lines of MailService in that the service layer has the NotificationService interface (defining the service), and NotificationServiceUtil class (providing the service statically), and I have an impl layer providing the NotificationServiceImpl (the implementation). I then registered the service in ext-spring.xml:

  <bean id="org.mitre.asias.portal.notification.service.NotificationService"
    class="org.mitre.asias.portal.notification.service.impl.NotificationServiceImpl">
    ...
  </bean>
  <bean id="org.mitre.asias.portal.notification.service.NotificationServiceUtil"
    class="org.mitre.asias.portal.notification.service.NotificationServiceUtil">
    <property name="service"
      ref="org.mitre.asias.portal.notification.service.NotificationService" />
  </bean>

Everything works as expected until I try to throw transactions into the mix. I have a single method in NotificationServiceImpl that needs to be transactional:

public void sendAndUpdate( Message message, List<Event> eventsToUpdate ) throws SystemException, MessagingException {
    for ( Event event : eventsToUpdate ) {
        eventLocalService.updateEvent( event );
    }
    Transport.send( message );
}

The idea being that if for some reason the sending of the message failed, the changes to the model objects would be rolled back so I could retry the send at a later time. I tried annotating this method with:

@Transactional( isolation = Isolation.PORTAL,
        rollbackFor = { PortalException.class, SystemException.class, MessagingException.class } )

But it did not take. I tried moving that annotation to the NotificationService interface, but still no dice. I enabled debug logging on org.hibernate.transaction.JDBCTransaction and can see that a transaction is never started for the call to that method, but one is started and committed for each updateEvent call in the loop.

Digging into the source, it appears that Liferay has a bean post processor called ServiceBeanAutoProxyCreator which has this code:

...
protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, TargetSource targetSource)
    throws BeansException {

    if (beanName.endsWith(_SERVICE_SUFFIX)) {
        return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
    }
    else {
        return DO_NOT_PROXY;
    }
}

private static final String _SERVICE_SUFFIX = "Service";
...

Which makes it look as if every bean whose name ends with Service (as my bean does: ...id="org.mitre.asias.portal.notification.service.NotificationService"...) should be wrapped in the ServiceBeanAopProxy which would add the transactional advice according to the service builder generated base-spring.xml:

<bean class="com.liferay.portal.spring.aop.ServiceBeanAutoProxyCreator">
    <property name="methodInterceptor" ref="serviceAdvice" />
</bean>
<bean class="com.liferay.portal.spring.context.PortletBeanFactoryCleaner" />
<bean class="com.liferay.portal.spring.context.PortletBeanFactoryPostProcessor" />
<bean class="com.liferay.portal.spring.bean.BeanReferenceAnnotationBeanPostProcessor" />
<bean id="portletClassLoader" class="com.liferay.portal.kernel.portlet.PortletClassLoaderUtil" factory-method="getClassLoader" />
<bean id="servletContextName" class="com.liferay.portal.kernel.portlet.PortletClassLoaderUtil" factory-method="getServletContextName" />
<bean id="basePersistence" abstract="true">
    <property name="dataSource" ref="liferayDataSource" />
    <property name="sessionFactory" ref="liferaySessionFactory" />
</bean>
<bean id="serviceAdvice" class="com.liferay.portal.monitoring.statistics.service.ServiceMonitorAdvice">
    <property name="monitoringDestinationName" value="liferay/monitoring" />
    <property name="nextMethodInterceptor" ref="asyncAdvice" />
</bean>
<bean id="asyncAdvice" class="com.liferay.portal.messaging.async.AsyncAdvice">
    <property name="defaultDestinationName" value="liferay/async_service" />
    <property name="nextMethodInterceptor" ref="threadLocalCacheAdvice" />
</bean>
<bean id="threadLocalCacheAdvice" class="com.liferay.portal.cache.ThreadLocalCacheAdvice">
    <property name="nextMethodInterceptor" ref="transactionAdvice" />
</bean>
<bean id="transactionAdvice" class="com.liferay.portal.spring.transaction.TransactionInterceptor">
    <property name="transactionAttributeSource" ref="transactionAttributeSource" />
    <property name="transactionManager" ref="liferayTransactionManager" />
</bean>
<bean id="transactionAttributeSource" class="com.liferay.portal.spring.transaction.AnnotationTransactionAttributeSource" />

Does anybody have any idea how I could make this work?

I am using Liferay 6.0 EE sp2 in a tomcat 6.0.32 container.

Lucas
  • 14,227
  • 9
  • 74
  • 124

1 Answers1

0

Try to move the @transactional annotation on to the interface (not on the method of the interface)

Liferay wraps transactions over beans which names end with Service (depends on the bean name not the class name).

Also the problem could be in placement of the code. Where do you define your bean? I suppose in extplugin? If you liked to extend Liferay transaction in your own portlet without service builder, it can get tricky. However it should be possible to share Liferay spring context in 6.1.

Miroslav Ligas
  • 1,287
  • 8
  • 22