2015-02-19 7 views
8

Я создал приложение Spring Boot с использованием версии 1.2.0 с функцией spring-boot-starter-data-jpa, и я использую MySQL. Я правильно настроил свои свойства MySQL в файле application.properties.Spring Boot + Spring Data JPA + Сделки не работают должным образом

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

@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 доступны Exception.

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

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

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

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

Кажется, 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

+0

Опубликуйте свой класс приложений и maven pom. Используемая вами версия Spring Boot должна использовать Spring 4.1.x, а не что-то старое, если там есть другая версия, у вас наверняка есть какие-то странные зависимости в вашем списке. –

+0

Добавлен pom.xml и Application.java –

+4

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

ответ

2

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

+1

Это поведение по умолчанию Spring, не нужно явно указывать. –

+0

Я столкнулся с такой же проблемой. Таким образом, я сохранил класс Exception, он решил – Abhinay

+0

Если это проверочное исключение, нам нужно явно указать. Если это исключенное исключение, такое как RuntimeException или любые подклассы, то весна автоматически выполнит транзакцию rollsback. –

0

I t задайте это с помощью 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> 

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

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

1

От comment на @ м-deinum :

Сделайте свой личный сервис public так же, как и метод Вы звоните.

Это похоже на трюк для нескольких пользователей. То же самое также охватывается в this answer, ссылаясь на manual saying:

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

Смежные вопросы