4
votes

We have Spring Data configured for JPA. A Service Transaction method doesn't get rolled back for an error (e.g. a DB ConstraintViolationException).

The closest I could find was this (Transaction not rolling back) Spring-data, JTA, JPA, Wildfly10 but we don't have any XML configuration, all of our configuration is Java-based.

Essentially, a service method looks like this: no errors are caught, everything thrown.

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)
public void insertEvent() throws Exception {
     // Part 1
     EventsT event = new EventsT(); 
     // populate it..
     eventsDAO.save(event);

     // Part 2 - ERROR HAPPENS HERE (Constraint Violation Exception)
     AnswersT answer = new AnswersT();
     // populate it..
     answersDAO.save(answer);   
}

Part 2 fails. But after the error and return, I see that the Event (Part 1) is still populated in the DB.

We also tried various combinations of @Transactional, nothing worked:

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = false)
@Transactional(readOnly = false)
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = ConstraintViolationException.class, readOnly = false)

Spring Data CRUD DAO Interfaces:

@Repository
public interface EventsDAO extends JpaRepository<EventsT, Integer> {

}

@Repository
public interface AnswersDAO extends JpaRepository<AnswersT, Integer> {

}

JpaConfig:

@Configuration
@EnableJpaRepositories(basePackages = "com.myapp.dao")
@PropertySource({ "file:${conf.dir}/myapp/db-connection.properties" })
public class JpaConfig {

    @Value("${jdbc.datasource}")
    private String dataSourceName;

    @Bean
    public Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<String, Object>();
        props.put("hibernate.dialect", PostgreSQL95Dialect.class.getName());
        //props.put("hibernate.cache.provider_class", HashtableCacheProvider.class.getName());
        return props;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(true);
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        hibernateJpaVendorAdapter.setDatabase(Database.POSTGRESQL);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws NamingException {
        return new JpaTransactionManager( entityManagerFactory().getObject() );
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
        LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
        lef.setDataSource(dataSource());
        lef.setJpaPropertyMap(this.jpaProperties());
        lef.setJpaVendorAdapter(this.jpaVendorAdapter());
        lef.setPackagesToScan("com.myapp.domain", "com.myapp.dao");
        return lef;
    }

    @Bean
    public DataSource dataSource() throws NamingException {
        return (DataSource) new JndiTemplate().lookup(dataSourceName);
    }   

}

Have there been any transaction rollback issues with Spring Data & JPA?

2
Just for clarification, you are using org.springframework.transaction.annotation.Transactional or javax.transaction.Transactional? And insertEvent() is the first method annotated as Transactional in the process? - lzagkaretos
The transactional annotation is org.springframework.transaction.annotation.Transactional. And yes, the insertEvent method is annotated with @Transactional as shown above. - gene b.
What are the DB? case MySQL: Rollback isn't possible without engine=InnoDB - Henrique Fernandes Cipriano
PostgreSQL. In fact, even if I try to do a Delete, Spring Data complains there's no transaction: javax.persistence.TransactionRequiredException: Executing an update/delete query. So a Transaction isn't found, even though I annotated it! - gene b.

2 Answers

4
votes

Believe it or not we fixed it. There were 2 parts to the solution:

1) Add @EnableTransactionManagement to JpaConfig as ledniov described, but that alone wasn't enough;

2) Also in JpaConfig in entityManagerFactory(), add the Service class package to the following setPackagesToScan. Previously, the domain object package was there, but the service object package was not. We added "myapp.service", the 2nd package.

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
    LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
    lef.setDataSource(dataSource());
    lef.setJpaPropertyMap(this.jpaProperties());
    lef.setJpaVendorAdapter(this.jpaVendorAdapter());
    lef.setPackagesToScan("myapp.domain", "myapp.service"); //NOTE: Service was missing
    return lef;
}
2
votes

You have to add @EnableTransactionManagement annotation to JpaConfig class in order to enable Spring's annotation-driven transaction management capability.