it-swarm.com.ru

Spring Boot + Spring Data JPA + Транзакции не работают должным образом

Я создал приложение Spring Boot, используя версию 1.2.0 с spring-boot-starter-data-jpa, и я использую MySQL . Я правильно настроил свои свойства MySQL в файле application.properties.

У меня есть простая сущность JPA, репозиторий Spring Data JPA и служба:

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

У меня есть следующий тест JUnit:

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

}

Здесь ожидается, что он не должен вставлять какие-либо записи в таблицу PERSON, так как он выдаст исключение при вставке объекта от третьего лица . Но это не откатывается, первые 2 записи вставляются и фиксируются.

Тогда я подумал о быстрой попытке с JPA EntityManager.

@PersistenceContext
private EntityManager em;

em.save(person);

Затем я получаю javax.persistence.TransactionRequiredException: транзакционный EntityManager недоступен Исключение.

После поисков в течение некоторого времени я сталкиваюсь с этой темой JIRA https://jira.spring.io/browse/SPR-11923 по той же теме.

Затем я обновил версию Spring Boot до 1.1.2, чтобы получить версию Spring старше 4.0.6.

Затем em.save (person) работает, как ожидалось, и Transaction работает нормально (означает, что выполняется откат всех вставок БД при возникновении RuntimeException).

Но даже в версиях Spring 4.0.5 + Spring Data JPA 1.6.0 транзакции не работают, если вместо em.persist (person) используется personRepository.save (person).

Похоже, что репозитории Spring Data JPA фиксируют транзакции.

Что мне не хватает? Как заставить аннотации уровня обслуживания @Transactional работать?

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

Измените Транзакционную аннотацию на @Transactional (rollbackFor = Exception.class)

3
Abhinay

От комментарий от @ m-deinum:

Сделайте ваш PersonService public так же, как метод, который вы вызываете.

Похоже, это помогло нескольким пользователям. То же самое также рассматривается в этот ответ , цитируя ручная поговорка :

При использовании прокси вы должны применять аннотацию @Transactional только для методов с публичной видимостью. Если вы делаете аннотацию защищенной, закрытые или видимые в пакете методы с аннотацией @Transactional, ошибка не возникает, но аннотированный метод не отображает настроены транзакционные настройки. Рассмотрите возможность использования AspectJ (см. .__ ниже), если вам нужно аннотировать непубличные методы.

2
eis

Я протестировал это с SpringBoot v1.2.0 и v1.5.2, и все работает должным образом, вам не нужно использовать менеджер сущностей ни @Transactional (rollbackFor = Exception.class).

Я не мог видеть, какую часть вы неправильно настроили, на первый взгляд все выглядит нормально, поэтому я просто публикую всю свою конфигурацию как ссылку с более обновленными аннотациями и встроенной базой данных 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>

Персона. Ява

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

* База данных очищается и повторно инициализируется для каждого теста, чтобы мы всегда проверяли на соответствие известному состоянию

0
Javier Sánchez