New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Untranslated UnexpectedRollbackException caused by JPA Integrity violation Exception due to deferred operations in MS SQL [SPR-3849] #8529

Closed
spring-issuemaster opened this Issue Sep 7, 2007 · 4 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

spring-issuemaster commented Sep 7, 2007

Dmitry V. Zemnitskiy opened SPR-3849 and commented

Copied from http://forum.springframework.org/showthread.php?p=140845
Also see http://forum.springframework.org/showthread.php?t=31531

I just checked with latest available spring (2.0.6), hibernate (3.2.5), hibernate entity manager and MS SQL database (both MSDE and MS SQL Server 2000), jtds driver.

As I see it is still not solved, that is, with declarative transactions and exception translation, having business manager class annotated with @Repository and business method annotated with @Transactional (no nested transactions), example configuration below:

Code:

<bean id="entityManagerFactory"
	class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
	<property name="persistenceUnitName" value="${jpa.persistence.unit}" />
	<property name="jpaVendorAdapter" ref="hibernateAdapter" />
</bean>

<bean name="hibernateAdapter"
	class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />

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

<bean
	class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean
	class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

<bean id="entityManager"
	class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
	<property name="entityManagerFactory"
		ref="entityManagerFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

I get UnexpectedRollbackException caused by integrity violation exception, because actual database operation and constraints verification are seems deferred to commit() in MS SQL (much like in Oracle I think).

Here's exception stack trace:

Code:

org.springframework.transaction.UnexpectedRollbackException: JPA transaction unexpectedly rolled back (maybe marked rollback-only after a failed operation); nested exception is javax.persistence.RollbackException: Error while commiting the transaction
Caused by:
javax.persistence.RollbackException: Error while commiting the transaction
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:71)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:433)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:662)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:632)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:314)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:117)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:166)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy34.handleChangeMsisdn(Unknown Source)
at com.vyke.mobile.server.io.ConnectionHandlerImpl.packetReceived(ConnectionHandlerImpl.java:254)

... removed application code calls ...

at java.lang.Thread.run(Unknown Source)

Caused by: org.hibernate.exception.ConstraintViolationException: could not update: [com.vyke.mobile.server.domain.Client#4]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2425)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2307)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2607)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:92)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
... 29 more
Caused by: java.sql.SQLException: Violation of UNIQUE KEY constraint 'UQ__Client__72C60C4A'. Cannot insert duplicate key in object 'Client'.
at net.sourceforge.jtds.jdbc.SQLDiagnostic.addDiagnostic(SQLDiagnostic.java:365)
at net.sourceforge.jtds.jdbc.TdsCore.tdsErrorToken(TdsCore.java:2781)
at net.sourceforge.jtds.jdbc.TdsCore.nextToken(TdsCore.java:2224)
at net.sourceforge.jtds.jdbc.TdsCore.getMoreResults(TdsCore.java:628)
at net.sourceforge.jtds.jdbc.JtdsStatement.processResults(JtdsStatement.java:525)
at net.sourceforge.jtds.jdbc.JtdsStatement.executeSQL(JtdsStatement.java:487)
at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.executeUpdate(JtdsPreparedStatement.java:421)
at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:23)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2403)
... 41 more

I found very similar problem description here :

http://forum.springframework.org/showthread.php?t=31531

it is dated by last year..

I was able to solve the problem only by verification during business method invocation if conflicting record already exists in database using extra select statement.

IMO it is very rough solution and quite bad problem in Spring, as the situation is quite common and it voids all efforts made to improve persistence exception
handling in Spring at all.

Regards,
Dima


Affects: 2.0.5, 2.0.6, 2.1 M3

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Sep 7, 2007

Juergen Hoeller commented

This is not so much a bug of Spring rather than a limitation in JPA's exception hierarchy. JPA simply throws a plain RollbackException in such a case, which suggests an unexpected rollback... That RollbackException is masking an underlying constraint violation, but that's unfortunately not reflected in the exception type at all. Native Hibernate has a finer-grained exception hierarchy there, clearly indicating a constraint violation, which allows Spring to cleanly detect and convert such an exception.

We can do some analysis of the underlying native exception even in the case of JPA... The mechanism is in place already: Our JpaDialect SPI has a persistence exception translator facility. I consider this JIRA issue as an enhancement request for introspecting the native Hibernate exception in our HibernateJpaDialect, also introspecting the underlying root cause of a JPA RollbackException (as handled in JpaTransactionManager). Scheduled for Spring 2.1.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Sep 7, 2007

Dmitry V. Zemnitskiy commented

Maybe I miss something related to nested transactions (why ever the UnexpectedRollbackException was added), but it should be relatively easy to solve this situation, as u explained above:

  1. Don't intercept javax.persistence.RollbackException in transaction manager, allowing it to be caught by ExceptionTranslator above -
    just handle the exception raised during commit() as exception raised during intercepted method call.

  2. Change exception translation logic in HibernateJpaDialect, so that if it can't translate exception itself, it recurses down to exception cause and tries to translate Exception's cause until it either finds exception it can translate or next cause is null. Maybe it can delegate to SessionFactoryUtils.convertHibernateAccessException() when doing that.

Though, maybe it would be good to look first at closed issues and investigate why ever this exception was added? My first impression after briefly scanning forum is its addition was related to nested transactions.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Sep 8, 2007

Juergen Hoeller commented

After some research, there is actually a bug hiding here - in the form of a misunderstanding of exception semantics: The JPA RollbackException will be thrown for any kind of commit failure, not just for unexpected rollback decisions (i.e. it is not equivalent to the JTA RollbackException). RollbackException is effectively just a holder for a specific exception; this is the case for Hibernate as well as for OpenJPA.

Hence I've revised JpaTransactionManager to convert JPA RollbackExceptions into Spring TransactionSystemExceptions, providing fine-grained commit exceptions through translating JPA RollbackException root causes before that default TransactionSystemException conversion. This means that a JpaDialect implementation will see the root cause of a RollbackException through a "translateExceptionIfPossible" call: By default, a more specific JPA PersistenceException (if any) will get introspected and translated there. This will make it into both 2.1 M4 and 2.0.7; the change will be available in tonight's nightly snapshots already.

However, Hibernate EntityManager unfortunately does not wrap specific JPA PersistenceExceptions with a RollbackException; it rather wraps its native HibernateExceptions with such a RollbackException. This means that the above-mentioned standard translation of JPA root causes won't kick in there. Furthermore, Hibernate EntityManager wraps constraint violations with EntityExistsExceptions, not clearly indicating a constraint violation (this even marked with FIXME in Hibernate EntityManager 3.3.1). So in order to still translate the actual root cause there, I've added native Hibernate exception conversion to HibernateJpaDialect, for any kind of PersistenceException with a HibernateException root cause. This is pretty powerful but may introduce subtle issues with backwards compatibility, hence I've only implemented that change for 2.1 M4. If you want equivalent behavior in 2.0.7, then extend HibernateJpaDialect and override "translateExceptionIfPossible" accordingly.

Juergen

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator

spring-issuemaster commented Sep 8, 2007

Dmitry V. Zemnitskiy commented

Juergen, thanks you very much for your help. I posted link to this fixed issue to both forum topics.

Good luck,
Dima

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment