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.