Spring Boot + Spring Data JPA + Transactions not working properly

jpa, spring, spring-boot, spring-data-jpa

Solution

From comment by @m-deinum:

Make your PersonService `public` as well as the method you are calling.

This seems to have done the trick for several users. The same thing is also covered in this answer, citing manual saying:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

Problem

I have created a Spring Boot application using 1.2.0 version with spring-boot-starter-data-jpa and I am using MySQL. I have configured my MySQL properties in application.properties file correctly. I have a simple JPA Entity, Spring Data JPA Repository and a Service as follows: ``` @Entity class Person { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; private String name; //setters & getters } @Repository public interface PersonRepository extends JpaRepository<Person, Integer>{ } import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional class PersonService { @Autowired PersonRepository personRepository; @Transactional void save(List<Person> persons){ for (Person person : persons) { if("xxx".equals(person.getName())){ throw new RuntimeException("boooom!!!"); } personRepository.save(person); } } } ``` I have the following JUnit test: ``` import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) public class ApplicationTests { @Autowired PersonService personService; @Test public void test_logging() { List<Person> persons = new ArrayList<Person>(); persons.add(new Person(null,"abcd")); persons.add(new Person(null,"xyz")); persons.add(new Person(null,"xxx")); persons.add(new Person(null,"pqr")); personService.save(persons); } } ``` The expectation here is it should not insert any records into PERSON table as it will throw Exception while inserting 3rd person object. But it is not getting rolled back, first 2 records are getting inserted and committed. Then I thought of quickly try with JPA EntityManager. ``` @PersistenceContext private EntityManager em; em.save(person); ``` Then I am getting javax.persistence.TransactionRequiredException: No transactional EntityManager available Exception. After googling for sometime I encounter this JIRA thread https://jira.spring.io/browse/SPR-11923 on the same topic. Then I updated the Spring Boot version to 1.1.2 to get Spring version older than 4.0.6. Then em.save(person) working as expected and Transaction is working fine (means it is rollbacking all the db inserts when RuntimeException occurred). But even with Spring 4.0.5 + Spring Data JPA 1.6.0 versions transactions are not working when personRepository.save(person) is used instead of em.persist(person). It seems Spring Data JPA repositories are committing the transactions. What am I missing? How to make Service level @Transactional annotations work? PS: Maven pom.xml ``` <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sivalabs</groupId> <artifactId>springboot-data-jpa</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-data-jpa</name> <description>Spring Boot Hello World</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.0.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.sivalabs.springboot.Application</start-class> <java.version>1.7</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> </project> ``` Application.java ``` @EnableAutoConfiguration @Configuration @ComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ```

Original source

Related problems