Created
March 20, 2017 15:20
-
-
Save yarosla/794fdff3a7c9448faa18d484066b4fa8 to your computer and use it in GitHub Desktop.
Could not commit JPA transaction: Transaction marked as rollbackOnly (exploring various scenarios for this exception) See http://stackoverflow.com/a/42899742/697313
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ys | |
import groovy.util.logging.Slf4j | |
import org.springframework.beans.factory.annotation.Autowired | |
import org.springframework.context.annotation.Bean | |
import org.springframework.context.annotation.Configuration | |
import org.springframework.context.annotation.PropertySource | |
import org.springframework.test.context.ContextConfiguration | |
import org.springframework.transaction.UnexpectedRollbackException | |
import org.springframework.transaction.annotation.Propagation | |
import org.springframework.transaction.annotation.Transactional | |
import spock.lang.Specification | |
// Note: DataSourceConfig.class, SessionConfig.class are Spring configurations defined elsewhere | |
@ContextConfiguration(classes = [TestConfig.class, DataSourceConfig.class, SessionConfig.class]) | |
@Slf4j | |
class SpringTransactionRollbackSpec extends Specification { | |
@Autowired | |
Service1 service1 | |
def "rollback from nested transactional"() { | |
when: | |
service1.invokeTransactional() | |
then: | |
UnexpectedRollbackException e = thrown() | |
e.message == 'Transaction rolled back because it has been marked as rollback-only' | |
e.cause == null | |
} | |
def "rollback from nested transactional readonly"() { | |
when: | |
service1.invokeTransactionalReadOnly() | |
then: | |
UnexpectedRollbackException e = thrown() | |
e.message == 'Transaction rolled back because it has been marked as rollback-only' | |
e.cause == null | |
} | |
def "rollback when invoking from readonly"() { | |
when: | |
service1.invokeTransactionalFromReadonly() | |
then: | |
UnexpectedRollbackException e = thrown() | |
e.message == 'Transaction rolled back because it has been marked as rollback-only' | |
e.cause == null | |
} | |
def "no rollback using noRollbackFor for nested transactional"() { | |
when: | |
service1.invokeTransactionalNoRollback() | |
then: | |
notThrown(Exception) | |
} | |
def "no rollback bypassing spring proxy"() { | |
when: | |
service1.invokeTransactionalLocal() | |
then: | |
notThrown(Exception) | |
} | |
def "no rollback when nested call is not transactional"() { | |
when: | |
service1.invokeNonTransactional() | |
then: | |
notThrown(Exception) | |
} | |
def "no rollback when nested call requires new transaction"() { | |
when: | |
service1.invokeTransactionalRequiresNew() | |
then: | |
notThrown(Exception) | |
} | |
def "no rollback when throwing checked exception"() { | |
when: | |
service1.invokeTransactionalChecked() | |
then: | |
notThrown(Exception) | |
} | |
static class Service1 { | |
@Autowired | |
Service2 service2 | |
@Transactional | |
void invokeTransactional() { | |
try { | |
service2.methodTransactional() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeTransactionalReadOnly() { | |
try { | |
service2.methodTransactionalReadOnly() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeTransactionalNoRollback() { | |
try { | |
service2.methodTransactionalNoRollback() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeTransactionalLocal() { | |
try { | |
methodTransactionalLocal() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional(readOnly = true) | |
void invokeTransactionalFromReadonly() { | |
try { | |
service2.methodTransactional() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeNonTransactional() { | |
try { | |
service2.methodNonTransactional() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeTransactionalChecked() { | |
try { | |
service2.methodTransactionalChecked() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void invokeTransactionalRequiresNew() { | |
try { | |
service2.methodTransactionalRequiresNew() | |
} catch (Exception e) { | |
log.info 'caught exception:', e | |
} | |
} | |
@Transactional | |
void methodTransactionalLocal() { | |
throw new RuntimeException('from methodTransactionalLocal') | |
} | |
} | |
static class Service2 { | |
void methodNonTransactional() { | |
throw new RuntimeException('from methodNonTransactional') | |
} | |
@Transactional | |
void methodTransactional() { | |
throw new RuntimeException('from methodTransactional') | |
} | |
@Transactional(readOnly = true) | |
void methodTransactionalReadOnly() { | |
throw new RuntimeException('from methodTransactionalReadOnly') | |
} | |
@Transactional(noRollbackFor = RuntimeException) | |
void methodTransactionalNoRollback() { | |
throw new RuntimeException('from methodTransactionalNoRollback') | |
} | |
@Transactional | |
void methodTransactionalChecked() throws Exception { | |
throw new Exception('from methodTransactionalChecked') | |
} | |
@Transactional(propagation = Propagation.REQUIRES_NEW) | |
void methodTransactionalRequiresNew() { | |
throw new RuntimeException('from methodTransactionalRequiresNew') | |
} | |
} | |
@Configuration | |
static class TestConfig { | |
@Bean | |
Service1 service1() { | |
return new Service1() | |
} | |
@Bean | |
Service2 service2() { | |
return new Service2() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment