Reputation: 1645
I have a somewhat complex transaction which does or does not create many different types of entities. Should it fail to create one of those entities due to a validation error, I'd like to simply catch the ConstraintValidationError and move on (allowing any other entities created in the same transaction to save successfully).
I can do this successfully but the validation error still marks my whole transaction for rollback, which I do not want.
I tried changing the single validating method which causes the ConstraintValidationError to have a:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
to force a new transaction, thinking it would mark only that new, inner transaction for rollback, but that doesn't seem to be the case.
The calling class is marked with a:
@Transactional(noRollbackFor=ConstraintViolationException.class)
but this doesn't help if the ConstraintViolationException is in the called method and I do not wish to add it to the called method.
What's the proper way to do something like this?
(Let me know if code is required -- the example is somewhat complex.)
EDIT (5/26/14):
When I try
progration = Propagation.REQUIRES_NEW
I see no change in the result. This particular log is from a test purposefully trying to save a 'department' entity object with a null description, which would be invalid:
11:46:43.599 [main] DEBUG org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'DefaultCourseManager.saveDepartment' with attribute: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT; ''
11:46:43.599 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'transactionManager'
11:46:43.599 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Found thread-bound EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3a90c13c] for JPA transaction
11:46:43.599 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Suspending current transaction, creating new transaction with name [edu.ucdavis.dss.dw.site.DefaultCourseManager.saveDepartment]
11:46:43.600 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@67b355c8] for JPA transaction
11:46:43.600 [main] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - begin
11:46:43.600 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtaining JDBC connection
11:46:43.601 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Obtained JDBC connection
11:46:43.601 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - initial autocommit status: true
11:46:43.601 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - disabling autocommit
11:46:43.601 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@388623ad]
11:46:43.601 [main] DEBUG org.springframework.data.repository.core.support.TransactionalRepositoryProxyPostProcessor$CustomAnnotationTransactionAttributeSource - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
11:46:43.602 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'transactionManager'
11:46:43.602 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Found thread-bound EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@67b355c8] for JPA transaction
11:46:43.602 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Participating in existing transaction
11:46:43.606 [main] DEBUG org.hibernate.SQL - select KeyValue from SurrogateKeys where TableName = 'Departments' for update
11:46:43.608 [main] DEBUG org.hibernate.SQL - insert into SurrogateKeys(TableName, KeyValue) values('Departments', ?)
11:46:43.610 [main] DEBUG org.hibernate.SQL - update SurrogateKeys set KeyValue = ? where KeyValue = ? and TableName = 'Departments'
11:46:43.612 [main] DEBUG org.hibernate.SQL - select KeyValue from SurrogateKeys where TableName = 'Departments' for update
11:46:43.612 [main] DEBUG org.hibernate.SQL - update SurrogateKeys set KeyValue = ? where KeyValue = ? and TableName = 'Departments'
11:46:43.613 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 1, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
11:46:43.613 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Initiating transaction commit
11:46:43.613 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@67b355c8]
11:46:43.613 [main] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - committing
11:46:43.613 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
11:46:43.614 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
11:46:43.614 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 1 insertions, 0 updates, 0 deletions to 1 objects
11:46:43.614 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
11:46:43.614 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
11:46:43.614 [main] DEBUG org.hibernate.internal.util.EntityPrinter - edu.ucdavis.dss.dw.entities.Department{courses=null, code=null, name=null, id=1}
11:46:43.620 [main] DEBUG org.hibernate.validator.resourceloading.PlatformResourceBundleLocator - ValidationMessages not found.
11:46:43.624 [main] DEBUG org.hibernate.validator.resourceloading.PlatformResourceBundleLocator - org.hibernate.validator.ValidationMessages found.
11:46:43.637 [main] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - rolling back
11:46:43.638 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - rolled JDBC Connection
11:46:43.638 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - re-enabling autocommit
11:46:43.639 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@67b355c8] after transaction
11:46:43.640 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
11:46:43.648 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Releasing JDBC connection
11:46:43.649 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Released JDBC connection
11:46:43.649 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Resuming suspended transaction after completion of inner transaction
11:46:43.651 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Initiating transaction rollback
11:46:43.651 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Rolling back JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3a90c13c]
11:46:43.651 [main] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl - rolling back
11:46:43.651 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - rolled JDBC Connection
11:46:43.651 [main] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction - re-enabling autocommit
11:46:43.652 [main] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3a90c13c] after transaction
11:46:43.652 [main] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
11:46:43.652 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
11:46:43.652 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Releasing JDBC connection
11:46:43.653 [main] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl - Released JDBC connection
11:46:43.655 [main] DEBUG com.github.springtestdbunit.DbUnitTestExecutionListener - Skipping @DatabaseTest expectation due to test exception class org.springframework.transaction.TransactionSystemException
11:46:43.656 [main] DEBUG org.dbunit.database.DatabaseDataSourceConnection - close() - start
11:46:43.657 [main] DEBUG org.springframework.test.context.support.DirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@1da2cb77 testClass = BannerManagerTestCase, testInstance = edu.ucdavis.dss.dw.site.BannerManagerTestCase@48f278eb, testMethod = testCatchesNullDepartment@BannerManagerTestCase, testException = org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction, mergedContextConfiguration = [MergedContextConfiguration@2f217633 testClass = BannerManagerTestCase, locations = '{}', classes = '{class edu.ucdavis.dss.dw.config.TestContextConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], class dirties context [false], class mode [null], method dirties context [false].
11:46:43.675 [main] DEBUG org.springframework.test.context.support.DirtiesContextTestExecutionListener - After test class: context [DefaultTestContext@1da2cb77 testClass = BannerManagerTestCase, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [MergedContextConfiguration@2f217633 testClass = BannerManagerTestCase, locations = '{}', classes = '{class edu.ucdavis.dss.dw.config.TestContextConfiguration}', contextInitializerClasses = '[]', activeProfiles = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], dirtiesContext [false].
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 7.299 sec <<< FAILURE!
Results :
Tests in error:
testCatchesNullDepartment(edu.ucdavis.dss.dw.site.BannerManagerTestCase): Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
If I'm not sure if I'm reading that correctly -- I see the null-valued department entity and then the information that it is rolling back but one would think it would indicate somewhere as to why it decided to rollback.
Upvotes: 2
Views: 4735
Reputation: 61538
You can run the problematic method in a new transaction. Instead of @Transactional(propagation = Propagation.NOT_SUPPORTED)
, you should use Propagation.REQUIRES_NEW:
@Transactional(propagation = Propagation.REQUIRES_NEW)
to force the creation of a new transaction. NOT_SUPPORTED
does not lead to the creation of a new transaction, it only suspends the current one.
Wrap the call to the method in a try/catch block. I am not sure what Exception you are going to get in a spring environment. In Java EE, it would be EJBException
.
Upvotes: 5