Spring Download + Spring JPA + Data Deals not working properly

I created a Spring boot application using version 1.2.0 with spring-boot-starter-data-jpa and I use MySQL. I configured my MySQL properties correctly in the application.properties file.

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 that it should not insert records into the PERSON table, since it will throw an exception when inserting a third party object. But he does not roll back, the first 2 entries are inserted and fixed.

Then I thought of a quick try using JPA EntityManager.

 @PersistenceContext private EntityManager em; em.save(person); 

Then I get javax.persistence.TransactionRequiredException: Missing Transactional EntityManager .

After I ever ran into a search, I came across this JIRA thread https://jira.spring.io/browse/SPR-11923 in the same thread.

Then I upgraded the Spring boot version to 1.1.2 to get Spring version older than 4.0.6 .

Then em.save (person) works as expected, and the transaction works fine (meaning that it rolls back all db inserts when a RuntimeException occurs).

But even with Spring 4.0.5 + Spring Data JPA 1.6.0 transactions, transactions do not work when personRepository.save (person) is used instead of em.persist (person) .

It seems that Spring JPA Data Warehouses are committing transactions.

What am I missing? How to annotate @Transactional service?

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); } } 
+8
spring spring-boot spring-data-jpa jpa
source share
3 answers

Change transaction annotation to @Transactional (rollbackFor = Exception.class)

+2
source share

From comment by @ m-deinum:

Make your PersonService public , as well as the method you call.

This seems like a trick for multiple users. The same is also described in this answer , referring to the user manual :

When using a proxy, you should use the @Transactional annotation only for methods with public visibility. If you make protected, private or batch-visible methods annotation with @Transactional annotation, an error does not occur, but the annotated method does not show the configured transaction settings. Consider using AspectJ (see below) if you need to annotate non-public methods.

+1
source share

I tested this with SpringBoot v1.2.0 and v1.5.2, and everything works as expected, you do not need to use entity manager nor @Transactional (rollbackFor = Exception.class).

I couldn’t see which part you misconfigured, everything seems fine at first glance, so I just put all my settings as a link with newer annotations and H2 built-in memory:

application.properties

 # Embedded memory database spring.jpa.hibernate.ddl-auto=create spring.datasource.url=jdbc:h2:~/test;AUTO_SERVER=TRUE 

pom.xml

 <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.demo</groupId> <artifactId>jpaDemo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> </dependencies> </project> 

Person.java

 @Entity public class Person { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Integer id; private String name; public Person() {} public Person(String name) {this.name = name;} public Integer getId() {return id;} public void setId(Integer id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} } 

PersonRepository.java

 @Repository public interface PersonRepository extends JpaRepository<Person, Integer> {} 

PersonService.java

 @Service public class PersonService { @Autowired PersonRepository personRepository; @Transactional public void saveTransactional(List<Person> persons){ savePersons(persons); } public void saveNonTransactional(List<Person> persons){ savePersons(persons); } private void savePersons(List<Person> persons){ for (Person person : persons) { if("xxx".equals(person.getName())){ throw new RuntimeException("boooom!!!"); } personRepository.save(person); } } } 

Application.java

 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

PersonServiceTest.java

 @RunWith(SpringRunner.class) @SpringBootTest public class PersonServiceTest { @Autowired PersonService personService; @Autowired PersonRepository personRepository; @Test public void person_table_size_1() { List<Person> persons = getPersons(); try {personService.saveNonTransactional(persons);} catch (RuntimeException e) {} List<Person> personsOnDB = personRepository.findAll(); assertEquals(1, personsOnDB.size()); } @Test public void person_table_size_0() { List<Person> persons = getPersons(); try {personService.saveTransactional(persons);} catch (RuntimeException e) {} List<Person> personsOnDB = personRepository.findAll(); assertEquals(0, personsOnDB.size()); } public List<Person> getPersons(){ List<Person> persons = new ArrayList() {{ add(new Person("aaa")); add(new Person("xxx")); add(new Person("sss")); }}; return persons; } } 

* The database is cleaned and reinitialized for each test, so we always check for a known state

0
source share

All Articles