Spring Boot + Spring Data JPA + 事务无法正常工作
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28606518/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot + Spring Data JPA + Transactions not working properly
提问by K. Siva Prasad Reddy
I have created a Spring Boot application using 1.2.0 version with spring-boot-starter-data-jpa and I am using MySQL. I have configured my MySQL properties in application.properties file correctly.
我使用带有 spring-boot-starter-data-jpa 的 1.2.0 版本创建了一个 Spring Boot 应用程序,并且我正在使用 MySQL。我已经在 application.properties 文件中正确配置了我的 MySQL 属性。
I have a simple JPA Entity, Spring Data JPA Repository and a Service as follows:
我有一个简单的 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);
}
}
}
I have the following JUnit test:
我有以下 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);
}
}
The expectation here is it should not insert any records into PERSON table as it will throw Exception while inserting 3rd person object. But it is not getting rolled back, first 2 records are getting inserted and committed.
这里的期望是它不应该将任何记录插入到 PERSON 表中,因为它会在插入第三个人对象时抛出异常。但它没有回滚,前 2 条记录被插入并提交。
Then I thought of quickly try with JPA EntityManager.
然后我想到了赶紧用 JPA EntityManager 试试。
@PersistenceContext
private EntityManager em;
em.save(person);
Then I am getting javax.persistence.TransactionRequiredException: No transactional EntityManager availableException.
然后我得到javax.persistence.TransactionRequiredException: No transactional EntityManager availableException。
After googling for sometime I encounter this JIRA thread https://jira.spring.io/browse/SPR-11923on the same topic.
在谷歌搜索一段时间后,我遇到了这个关于同一主题的JIRA 线程https://jira.spring.io/browse/SPR-11923。
Then I updated the Spring Boot version to 1.1.2to get Spring version older than 4.0.6.
然后我将 Spring Boot 版本更新为1.1.2以获得早于4.0.6 的Spring 版本。
Then em.save(person)working as expected and Transaction is working fine (means it is rollbacking all the db inserts when RuntimeException occurred).
然后em.save(person)按预期工作并且事务工作正常(意味着它在发生 RuntimeException 时回滚所有数据库插入)。
But even with Spring 4.0.5 + Spring Data JPA 1.6.0 versions transactions are not working when personRepository.save(person)is used instead of em.persist(person).
但即使使用 Spring 4.0.5 + Spring Data JPA 1.6.0 版本,当使用personRepository.save(person)而不是em.persist(person)时,事务也不起作用。
It seems Spring Data JPA repositories are committing the transactions.
似乎 Spring Data JPA 存储库正在提交事务。
What am I missing? How to make Service level @Transactionalannotations work?
我错过了什么?如何使服务级别@Transactional注释起作用?
PS:
PS:
Maven pom.xml
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
应用程序.java
@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
采纳答案by eis
From commentby @m-deinum:
来自@m-deinum 的评论:
Make your PersonService
public
as well as the method you are calling.
制作您的 PersonService
public
以及您正在调用的方法。
This seems to have done the trick for several users. The same thing is also covered in this answer, citing manual saying:
对于一些用户来说,这似乎已经成功了。这个答案也涵盖了同样的事情,引用手册说:
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
使用代理时,您应该仅将 @Transactional 注释应用于具有公共可见性的方法。如果您使用 @Transactional 注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示配置的事务设置。如果您需要注释非公共方法,请考虑使用 AspectJ(见下文)。
回答by Abhinay
Change Transactional annotation to @Transactional(rollbackFor=Exception.class)
将事务注释更改为@Transactional(rollbackFor=Exception.class)
回答by Javier Sánchez
I tested this with SpringBoot v1.2.0 and v1.5.2 and all is working as expected, you don't need to use entity manager neither @Transactional(rollbackFor=Exception.class).
我用 SpringBoot v1.2.0 和 v1.5.2 对此进行了测试,一切都按预期工作,您不需要使用实体管理器,也不需要使用 @Transactional(rollbackFor=Exception.class)。
I could not see which part you misconfigured, all seems fine at first glance, so I am just posting all my config as reference with more updated annotations and H2 embedded memory DB:
我看不到您配置错误的部分,乍一看似乎一切都很好,所以我只是发布我的所有配置作为参考,其中包含更多更新的注释和 H2 嵌入式内存数据库:
application.properties
应用程序属性
# Embedded memory database
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:h2:~/test;AUTO_SERVER=TRUE
pom.xml
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
人.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
PersonRepository.java
@Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {}
PersonService.java
人员服务.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
应用程序.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
PersonServiceTest.java
人员服务测试.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;
}
}
*The database is cleared and re-initialized for each test so that we always validate against a known state
*每次测试都会清除并重新初始化数据库,以便我们始终针对已知状态进行验证