2015-02-19 17 views
8

Ich habe eine Spring Boot-Anwendung mit der Version 1.2.0 mit Spring-Boot-Starter-Daten-JPA erstellt und ich benutze MySQL. Ich habe meine MySQL-Eigenschaften in der Datei application.properties korrekt konfiguriert.Spring Boot + Spring Daten JPA + Transaktionen funktioniert nicht richtig

Ich habe eine einfache JPA Entity, Spring Data JPA Repository und einen Dienst wie folgt:

@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);   
     } 
    } 
} 

Ich habe folgendes 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); 
    } 

} 

Die Erwartung, hier ist es nicht einsetzen sollte Alle Einträge in die PERSON-Tabelle, da beim Einlegen von Objekten der 3. Person eine Ausnahme ausgelöst wird. Aber es wird nicht zurückgerollt, zuerst werden 2 Datensätze eingefügt und festgeschrieben.

Dann dachte ich daran, schnell mit JPA EntityManager zu versuchen.

@PersistenceContext 
private EntityManager em; 

em.save(person); 

Dann bekomme ich javax.persistence.TransactionRequiredException: Nein Transaktions EntityManager verfügbar Ausnahme.

Nach googlen für einige Zeit begegne ich diesem JIRA-Thread https://jira.spring.io/browse/SPR-11923 zum gleichen Thema.

Dann aktualisiert ich die Frühlings-Boot-Version 1.1.2 Spring-Version älter als 4.0.6 zu bekommen.

Dann em.save (Person) funktioniert wie erwartet und Transaktion funktioniert gut (bedeutet, es Rollback alle DB-Einfügungen, wenn RuntimeException aufgetreten ist).

Aber auch mit Frühling-4.0.5 + Spring Data JPA 1.6.0 Versionen Transaktionen funktionieren nicht, wenn personRepository.save (Person) statt em.persist (Person) verwendet wird.

Es scheint, dass Spring Data JPA-Repositorys die Transaktionen festschreiben.

Was fehlt mir? Wie macht man Service Level @Transactional Anmerkungen funktionieren?

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

+0

Veröffentlichen Sie Ihre Anwendungsklasse und Maven Pom. Die Spring Boot-Version, die Sie verwenden, sollte Spring 4.1.x und nicht etwas Altes verwenden, wenn es eine andere Version gibt, haben Sie wahrscheinlich einige seltsame Abhängigkeiten in Ihrer Liste. –

+0

Hinzugefügt pom.xml und Application.java –

+4

Machen Sie Ihren 'PersonService'' public' sowie die Methode, die Sie aufrufen. –

Antwort

2

ändern Transactional Annotation @Transactional (rollbackFor = Exception.class)

+1

Dies ist das Standard-Spring-Verhalten, das nicht explizit angegeben werden muss. –

+0

Ich sah das gleiche Problem. So behielt ich meine Exception-Klasse, löste es – Abhinay

+0

Wenn es Ausnahme aktiviert ist, müssen wir explizit angeben. Wenn es eine Ausnahme wie RuntimeException oder Unterklassen davon ist, wird Spring die Transaktion automatisch zurücksetzen. –

0

Ich t Wenn dies mit SpringBoot v1.2.0 und v1.5.2 durchgeführt wurde und alles wie erwartet funktioniert, müssen Sie weder den Entity Manager noch @Transactional (rollbackFor = Exception.class) verwenden.

konnte ich nicht sehen, was Sie falsch konfigurierte trennen, die alle auf den ersten Blick in Ordnung scheint, so dass ich bin Entsendung nur alle meine Config als Referenz mit aktuelleren Annotationen und H2 Embedded-Speicher DB:

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

0.123.
@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; 
    } 
} 

* Die Datenbank wird gelöscht und für jeden Test neu initialisiert, so dass wir immer gegen einen Staat bekannt validieren

1

Von comment von @ m-Deinum :

Machen Sie Ihre PersonService public sowie die Verfahren Du rufst an.

Dies scheint für mehrere Benutzer den Trick getan zu haben. Dasselbe ist auch in this answer bedeckt, unter Berufung auf manual saying:

Wenn Proxies verwenden, sollten Sie die @Transactional Anmerkung nur auf Verfahren mit öffentlicher Sichtbarkeit gelten. Wenn Sie geschützte, Private oder Package-Visible-Methoden mit der Annotation @Transactional, kommentieren, wird kein Fehler ausgelöst, aber die mit Annotationen versehene Methode zeigt nicht die konfigurierten Transaktionseinstellungen. Erwägen Sie die Verwendung von AspectJ (siehe unten unter ), wenn Sie nicht öffentliche Methoden mit Anmerkungen versehen müssen.

Verwandte Themen