Skip to content

Instantly share code, notes, and snippets.

@yarosla
Created March 20, 2017 15:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save yarosla/794fdff3a7c9448faa18d484066b4fa8 to your computer and use it in GitHub Desktop.
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
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