transaction-management-enabled and message propagation issue
stefrem Sep 26, 2011 8:44 AMHi,
I work on technical stack with Hibernate 3.4.0.GA for persistance layer (API JPA 1.0 when we could),
Hibernate Validator 3.1.0.GA for domain validation, Spring 3.0.6 for service layer,
SEAM 2.2.2.Final for presentation layer and conversation management(with extended persistence context) and
JSF 1.2 (myfaces 1.2.10, tomahawk 1.1.11 et richfaces 3.3.3) for UI Layer.
Currently, I've got a strange problem with error messages in JSF layer (h: message or h:messages).
My main config for this problem, is :
Into component.xml :
<persistence:managed-persistence-context name="entityManagerManaged" entity-manager-factory="#{springEntityManagerFactory}" auto-create="true" /> <factory name="hibernateSession" scope="STATELESS" auto-create="true" value="#{entityManagerManaged.delegate}"/> <spring:spring-transaction platform-transaction-manager="#{transactionManager}" /> <core:init debug="false" transaction-management-enabled="false" security enabled="false"/>
Into applicationContext.xml :
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="springEntityManagerFactory"> <property name="persistenceUnitName" value="persistenceUnitRedaction"/> <property name="dataSource" ref="defaultDatasource"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> </property> </bean> <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager"> <property name="entityManagerFactory" ref="springEntityManagerFactory"/> </bean> <!-- transaction --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- ====================== DATASOURCE DEFINITION ====================== --> <!-- Configuration de Seam Session Factory --> <bean id="seamSessionFactory" class="org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean"> <property name="sessionName" value="hibernateSession"/> </bean> <bean id="hibernateTemplateSeam" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="seamSessionFactory" /> <!-- Config pour le flush mode MANUAL/NEVER --> <property name="checkWriteOperations" value="false" /> </bean>
Also this is my use cases :
In my component.xml, I set this property transaction-management-enabled to false.
I've got an object number property annotated like this :
/** * * * @return the number */ @Column( name="number", nullable = false, unique = true ) @NotEmpty (message="Number must be completed" ) @Length(min = 4, max = 21) public String getNumber(){ return this.number; }
Into page.xml, I've got this :
<exception class="org.springframework.dao.DataIntegrityViolationException" log="true" log-level="WARN"> <redirect > <message>Object already exists</message> </redirect> </exception>
Into my xhtml pages, I've got this :
... <s:validateAll > <t:inputText id="number" value="#{controler.object.number}" label="Object number" required="true" readonly="#{!controler.activeEditMode}" forceId="true"> f:converter converterId="EmptyToNullConverter" /> </t:inputText> <t:message for="number" styleClass="error" /> <br /> </s:validateAll> <br /> <t:messages globalOnly="true" styleClass="error" /> <br /> // Button qui declenche le save dans le component seam named controler <t:commandButton action="#{controler.validate()}" rendered="#{controler.activeEditMode}" value="Validate"/> ...
I call save method (ou un saveOrUpdate) with one number already exist.
I've got exception stack like this :
javax.servlet.ServletException: Error calling action method of component with id j_id19:j_id75 at javax.faces.webapp._ErrorPageWriter.throwException(_ErrorPageWriter.java:582) at javax.faces.webapp.FacesServlet.handleLifecycleException(FacesServlet.java:298) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:192) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:392) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:530) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83) at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:90) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.web.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:42) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:206) at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290) at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:388) at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:515) at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:60) at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69) at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:279) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662) Caused by: javax.faces.FacesException: Error calling action method of component with id j_id19:j_id75 at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:72) at javax.faces.component.UICommand.broadcast(UICommand.java:127) at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:329) at org.ajax4jsf.component.AjaxViewRoot.broadcastEventsForPhase(AjaxViewRoot.java:304) at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:261) at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:474) at org.apache.myfaces.lifecycle.InvokeApplicationExecutor.execute(InvokeApplicationExecutor.java:32) at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:103) at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:76) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:183) ... 44 more Caused by: javax.faces.el.EvaluationException: javax.el.ELException: /pages/affaire.xhtml @124,124 action="#{controler.validate()}": org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update at javax.faces.component._MethodExpressionToMethodBinding.invoke(_MethodExpressionToMethodBinding.java:82) at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:57) ... 53 more Caused by: javax.el.ELException: /pages/affaire.xhtml @124,124 action="#{controler.validate()}": org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:74) at javax.faces.component._MethodExpressionToMethodBinding.invoke(_MethodExpressionToMethodBinding.java:78) ... 54 more Caused by: org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [update Object set mailSuiEch=?, fraisRepro=?, number=?, objet=? where id=?]; constraint [REDACV3.SYS_C00395532]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:637) at org.springframework.orm.hibernate3.SpringSessionSynchronization.translateException(SpringSessionSynchronization.java:160) at org.springframework.orm.hibernate3.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:148) at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:95) at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:927) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:737) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) at $Proxy45.save(Unknown Source) at com.agysoft.marco.redaction.web.AffaireControler.valider(AffaireControler.java:69) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.seam.util.Reflections.invoke(Reflections.java:22) at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:32) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56) at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68) at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:77) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68) at org.jboss.seam.core.ConversationInterceptor.aroundInvoke(ConversationInterceptor.java:65) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68) at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68) at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107) at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185) at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103) at com.agysoft.marco.redaction.web.AffaireControler_$$_javassist_seam_3.valider(AffaireControler_$$_javassist_seam_3.java) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:335) at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:280) at org.jboss.el.parser.AstMethodSuffix.getValue(AstMethodSuffix.java:59) at org.jboss.el.parser.AstMethodSuffix.invoke(AstMethodSuffix.java:65) at org.jboss.el.parser.AstValue.invoke(AstValue.java:96) at org.jboss.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:276) at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:68) ... 55 more Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94) at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.seam.persistence.HibernateSessionInvocationHandler.invoke(HibernateSessionInvocationHandler.java:82) at $Proxy68.flush(Unknown Source) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.jboss.seam.ioc.spring.SeamManagedSessionFactoryBean$SeamManagedSessionHandler.invoke(SeamManagedSessionFactoryBean.java:300) at $Proxy69.flush(Unknown Source) at org.springframework.orm.hibernate3.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:145) ... 95 more Caused by: java.sql.BatchUpdateException: ORA-00001: unique constraint (REDACV3.SYS_C00395532) violated at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10070) at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:213) at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723) at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70) at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
In this case, Rollback is done correctly and I've got error message Object already exists
displayed correctly to my user.
I precise that in this config, hibernate validator messages works correctly.
My problem is when I would rollback transaction into presentation layer (Seam component).
I forced this behavior with this lines :
public String validate() { boolean test = true; this.objectService.save(this.object); if (test) { throw new RuntimeException("Test runtime exception in presentation layer"); } return "validate"; }
With this lines and config transaction-management-enabled to false , DataIntegrityViolationException is raised , Rollback is done and I've got error messages Object already exists
correctly displayed.
On the other side, if number filled by user is unique and I call my validate
method also save
method (spring and hibernate layer) do flush and commit and RuntimeException raised with redirect error message display but rollback comes too late (or maybe don't rollback) and my object is persist with my number update.
I precise that in this alternate config, hibernate validator messages works also correctly.
Consequently, I activate
transaction-management-enabled="true".
But I've got a very strange behavior :
When DataIntegrityViolationException is raised (number already exists in DBRMS),
Rollback is done but error message Object already exists
DIDN'T DISPLAY to my user.
On the other side, if number filled by user is unique and I call my validate
method also save
method (spring and hibernate layer) don't comit and RuntimeException raised with redirect error message display and THIS TIME rollback works fine and my object is not persist with my update.
I test many things to resolve this issue:
- Add into component.xml, this lines
<component name="org.jboss.seam.transaction.facesTransactionEvents"> <property name="transactionFailedMessageEnabled">false</property> </component>
- Add into page.xml this line (to redirect to another page) :
<exception class="org.springframework.dao.DataIntegrityViolationException" log="true" log-level="WARN"> <redirect view-id="/pages/errors.xhtml" > <message>object already exists</message> </redirect> </exception>
- Replace into xhtml this lines :
<t:messages globalOnly="true" styleClass="error" />
by
<t:messages styleClass="error" />
I saw other ressources and JIRA bugs reports :
- http://seamframework.org/Documentation/UnifiedErrorPageAndExceptionHandling
- http://www.seamframework.org/Community/BugInFacesMessagePropagation
- http://seamframework.org/Community/EndlessLoopInExceptionHandlingDuringCommit
- https://issues.jboss.org/browse/JBSEAM-1855
- https://issues.jboss.org/browse/JBSEAM-2139
- https://issues.jboss.org/browse/JBSEAM-4168
I didn't find any solution to my issue and many people don't talk about this property :
transaction-management-enabled="true"
I would really activate this property to manage exception and transaction in presentation/UI layer but I would also display error messages into below layer (service / dao ) like I don't active this property.
I wouldn't add catch exception or something like this (transform exception, etc) to manage exception in my presentation layer.
I find code very clean like this but if I must do...
I rather not use
@Begin(flushMode=manual)
for other reason that above ( It's maybe not a solution :) ).
Can you help me please to find a solution or a workaround ? Maybe is a bug ?
Thanks Stephane.