web-dev-qa-db-ja.com

Spring Boot + Spring Data JPA + Transactions not working working

1.2.0バージョンとspring-boot-starter-data-jpaを使用してSpring Bootアプリケーションを作成し、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-1192 に遭遇します。

次に、Spring Bootバージョンを1.1.2に更新して、Springバージョンを4.0.6より古いバージョンにしました。

次に、em.save(person)は期待どおりに動作し、トランザクションは正常に動作しています(つまり、RuntimeExceptionが発生したときにすべてのdb挿入をロールバックしています)。

ただし、Spring 4.0.5 + Spring Data JPA 1.6.0バージョンでも、personRepository.save(person)emの代わりに使用されている場合、トランザクションが機能しません.persist(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);
    }
}
11

From comment by @ m-deinum:

PersonService publicと、呼び出すメソッドを作成します。

これは、何人かのユーザーにとってトリックをしたようです。同じことは この答え にも含まれています 手動で言う を引用して:

プロキシを使用する場合、@ Transactionalアノテーションを公開するメソッドにのみ適用する必要があります。保護されたメソッド、プライベートメソッド、またはパッケージ表示メソッドに@Transactionalアノテーションを付けると、エラーは発生しませんが、アノテーションが付けられたメソッドは、構成されたトランザクション設定を示しません。非公開メソッドに注釈を付ける必要がある場合は、AspectJ(下記参照)の使用を検討してください。

2
eis

Transactionalアノテーションを@Transactional(rollbackFor = Exception.class)に変更します

4
Abhinay

私はこれをSpringBoot v1.2.0およびv1.5.2でテストしましたが、すべて期待どおりに機能し、@ Transactional(rollbackFor = Exception.class)でもエンティティマネージャーを使用する必要はありません。

私はあなたが誤って設定した部分を見ることができませんでした、一見するとすべて問題ないようですので、より多くの更新された注釈とH2組み込みメモリ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

@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