web-dev-qa-db-fra.com

Les opérations Spring Boot + Spring Data JPA + ne fonctionnent pas correctement

J'ai créé une application Spring Boot utilisant la version 1.2.0 avec spring-boot-starter-data-jpa et j'utilise MySQL. J'ai correctement configuré mes propriétés MySQL dans le fichier application.properties.

J'ai une simple entité JPA, un référentiel Spring Data JPA et un service comme suit:

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

J'ai le test JUnit suivant:

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

}

L’attente est qu’il ne faut pas insérer d’enregistrements dans la table PERSON car cela lancera Exception lorsqu’on insérera un objet à la 3ème personne.

Ensuite, j'ai pensé à essayer rapidement avec JPA EntityManager.

@PersistenceContext
private EntityManager em;

em.save(person);

Ensuite, je reçois javax.persistence.TransactionRequiredException: aucun EntityManager transactionnel disponible Exception.

Après avoir googlé pendant quelque temps, je rencontre ce fil JIRA https://jira.spring.io/browse/SPR-11923 sur le même sujet.

Ensuite, j'ai mis à jour la version de Spring Boot avec 1.1.2 pour obtenir une version de Spring plus ancienne que 4.0.6 .

Puis em.save (person) fonctionne comme prévu et Transaction fonctionne correctement (cela signifie que toutes les insertions de base de données sont annulées lorsque RuntimeException est survenu).

Mais même avec Spring 4.0.5 + Spring Data, les transactions de versions JPA 1.6.0 ne fonctionnent pas lorsque personRepository.save (person) est utilisé à la place de em.persist (person) .

Il semble que les référentiels Spring Data JPA engagent les transactions.

Qu'est-ce que je rate? Comment faire en sorte que Service level @Transactional annotations fonctionne?

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);
    }
}
9
K. Siva Prasad Reddy

Remplacez l'annotation transactionnelle par @Transactional (rollbackFor = Exception.class).

3
Abhinay

De comment de @ m-deinum:

Faites en sorte que votre PersonService public ainsi que la méthode que vous appelez.

Cela semble avoir fait l'affaire pour plusieurs utilisateurs. La même chose est également couverte dans cette réponse , citant manuel disant :

Lorsque vous utilisez des mandataires, vous devez appliquer l'annotation @Transactional Uniquement aux méthodes accessibles au public. Si vous annotez des méthodes protégées, Privées ou visibles du package avec l'annotation @Transactional, Aucune erreur n'est générée, mais la méthode annotée ne présente pas les paramètres transactionnels configurés [. Pensez à utiliser AspectJ (voir Ci-dessous) si vous devez annoter des méthodes non publiques.

2
eis

J'ai testé cela avec SpringBoot v1.2.0 et v1.5.2 et tout fonctionne normalement, vous n'avez pas besoin d'utiliser le gestionnaire d'entités ni @Transactional (rollbackFor = Exception.class).

Je ne voyais pas quelle partie vous avez mal configurée, tout semble aller pour le mieux, alors je publie simplement toute ma configuration comme référence avec des annotations plus récentes et une base de données de mémoire intégrée H2:

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

* La base de données est effacée et réinitialisée pour chaque test afin que nous puissions toujours valider par rapport à un état connu

0
Javier Sánchez