[英]@Transactional service methods rollback hibernate changes
我在 @Service 类中有一个方法,它在两个不同的 @Service 类中调用两个不同的方法。 这两种不同的方法在数据库中保存两个实体(通过休眠),它们都可能抛出一些异常。 我希望如果抛出异常,独立于哪个 @Service 方法,所有更改都会回滚。 所以在数据库中创建的所有实体都被删除了。
//entities
@Entity
public class ObjectB{
@Id
private long id;
...
}
@Entity
public class ObjectC{
@Id
private long id;
...
}
//servicies
@Service
@Transactional
public class ClassA{
@Autowired
private ClassB classB;
@Autowired
private ClassC classC;
public void methodA(){
classB.insertB(new ObjectB());
classC.insertC(new ObjectC());
}
}
@Service
@Transactional
public class ClassB{
@Autowired
private RepositoryB repositoryB;
public void insertB(ObjectB b){
repositoryB.save(b);
}
}
@Service
@Transactional
public class ClassC{
@Autowired
private RepositoryC repositoryC;
public void insertC(ObjectC c){
repositoryC.save(c);
}
}
//repositories
@Repository
public interface RepositoryB extends CrudRepository<ObjectB, String>{
}
@Repository
public interface RepositoryC extends CrudRepository<ObjectC, String>{
}
我想要ClassA的methodA,一旦从methodB或methodC抛出异常,它就会回滚数据库内的所有更改。 但它不会那样做。 异常后所有更改仍然存在......我错过了什么? 我应该添加什么才能使其按我的意愿工作? 我正在使用 Spring Boot 2.0.6! 我没有特别配置任何东西来使交易工作!
编辑 1
如果可以的话,这是我的主要课程:
@SpringBootApplication
public class JobWebappApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(JobWebappApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(JobWebappApplication.class, args);
}
}
当抛出异常时,这是我在控制台中看到的:
Completing transaction for [com.example.ClassB.insertB]
Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@df9d400] bound to thread [http-nio-8080-exec-7]
Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@1d1ad46b] for key [HikariDataSource (HikariPool-1)] bound to thread [http-nio-8080-exec-7]
Getting transaction for [com.example.ClassC.insertC]
Completing transaction for [com.example.ClassC.insertC] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Completing transaction for [com.example.ClassA.methodA] after exception: java.lang.RuntimeException: runtime exception!
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: runtime exception!
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Removed value [org.springframework.jdbc.datasource.ConnectionHolder@1d1ad46b] for key [HikariDataSource (HikariPool-1)] from thread [http-nio-8080-exec-7]
Removed value [org.springframework.orm.jpa.EntityManagerHolder@31d4fbf4] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@df9d400] from thread [http-nio-8080-exec-7]
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: runtime exception!] with root cause
似乎每次调用一个方法都会创建一个新的事务! 发生 RuntimeException 后不回滚任何内容!
编辑 2
这是 pom.xml 依赖文件:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
这是 application.properties 文件:
spring.datasource.url=jdbc:mysql://localhost:3306/exampleDB?useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.show-sql=true
logging.level.org.springframework.transaction=TRACE
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver.class=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.locationId.new_generator_mappings=false
解决方案
感谢@M.Deinum 我找到了解决方案!
我使用了错误的数据库引擎 (MyISAM),它不支持事务! 所以我用支持事务的“InnoDB”更改了表引擎类型。 我所做的是这样的:
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
现在抛出的所有 RuntimeExceptions 使事务回滚其中所做的所有更改。
警告:我注意到,如果抛出不是 RuntimeException 子类的异常,则不会应用回滚,并且所有已完成的更改都保留在数据库中。
您要实现的目标应该是开箱即用的。 检查您的弹簧配置。
确保您创建了TransactionManager
bean 并确保您在某些 spring @Configuration
上放置了@EnableTransactionManagement
注释。 该注解负责注册必要的 Spring 组件,这些组件为注解驱动的事务管理提供支持,例如TransactionInterceptor
和基于代理或基于 AspectJ 的建议,它们在调用@Transactional
方法时将拦截器编织到调用堆栈中。
请参阅链接的文档。
如果您使用的是spring-boot
,如果您在类路径上有PlatformTransactionManager
类,它应该会自动为您添加此注释。
另外,请注意检查异常不会触发事务的回滚。 只有运行时异常和错误才会触发回滚。 当然,您可以使用rollbackFor
和noRollbackFor
注释参数配置此行为。
编辑
当您澄清您正在使用 spring-boot 时,答案是:一切都应该在没有任何配置的情况下工作。
这是 spring-boot 版本2.1.3.RELEASE
最小 100% 工作示例(但应该适用于任何版本的 ofc):
依赖项:
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtimeOnly('com.h2database:h2') // or any other SQL DB supported by Hibernate
compileOnly('org.projectlombok:lombok') // for getters, setters, toString
用户实体:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
@Setter
@ToString
public class User {
@Id
@GeneratedValue
private Integer id;
private String name;
}
图书实体:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@Setter
@ToString
public class Book {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private User author;
private String title;
}
用户存储库:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
书库:
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Integer> {
}
用户服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
public User saveUser(User user) {
// return userRepository.save(user);
userRepository.save(user);
throw new RuntimeException("User not saved");
}
public List<User> findAll() {
return userRepository.findAll();
}
}
订书服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
@Component
public class BookService {
@Autowired
private BookRepository bookRepository;
public Book saveBook(Book book) {
return bookRepository.save(book);
}
public List<Book> findAll() {
return bookRepository.findAll();
}
}
复合服务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Component
public class CompositeService {
@Autowired
private UserService userService;
@Autowired
private BookService bookService;
public void saveUserAndBook() {
User user = new User();
user.setName("John Smith");
user = userService.saveUser(user);
Book book = new Book();
book.setAuthor(user);
book.setTitle("Mr Robot");
bookService.saveBook(book);
}
}
主要的:
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class JpaMain {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaMain.class)
.web(WebApplicationType.NONE)
.properties("logging.level.org.springframework.transaction=TRACE")
.run(args);
}
@Bean
public CommandLineRunner run(CompositeService compositeService, UserService userService, BookService bookService) {
return args -> {
try {
compositeService.saveUserAndBook();
} catch (RuntimeException e) {
System.err.println("Exception: " + e);
}
System.out.println("All users: " + userService.findAll());
System.out.println("All books: " + bookService.findAll());
};
}
}
如果您运行 main 方法,您应该看到在 DB 中没有找到任何书籍或用户。 事务回滚。 如果您从UserService
删除throw new RuntimeException("User not saved")
行,则两个实体都将被很好地保存。
您还应该在TRACE
级别看到org.springframework.transaction
包的日志,例如,您将看到:
Getting transaction for [demo.jpa.CompositeService.saveUserAndBook]
然后在抛出异常之后:
Completing transaction for [demo.jpa.CompositeService.saveUserAndBook] after exception: java.lang.RuntimeException: User not saved
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: User not saved
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
这里No relevant rollback rule found: applying default rules
意味着将应用DefaultTransactionAttribute
定义的规则来确定是否应该回滚事务。 这些规则是:
默认情况下回滚运行时但未检查的异常。
RuntimeException
是运行时异常,因此事务将被回滚。
行Clearing transaction synchronization
是实际应用回滚的地方。 您将看到一些其他Applying rules to determine whether transaction should rollback
消息,因为@Transactional
方法嵌套在这里(从CompositeService.saveUserAndBook
调用的UserService.saveUser
并且两个方法都是@Transactional
),但它们所做的只是确定未来操作的规则(在事务同步点)。 实际的回滚只会在最外面的@Transactional
方法出口完成一次。
从 spring 3.1 开始,如果您在类路径上使用 spring-data-* 或 spring-tx 依赖项,则默认情况下将启用事务管理。
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
但是检查 Springs Transactional 注释我们可以看到,如果异常不是 RuntimeException 的扩展,您需要通知参数 rollbackFor。
/**
* Defines zero (0) or more exception {@link Class classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>By default, a transaction will be rolling back on {@link RuntimeException}
* and {@link Error} but not on checked exceptions (business exceptions). See
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* @see #rollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
一个简单的@Transactional(rollbackFor = Exception.class)应该可以工作
您在这里尝试实现的事情是不可能的,因为一旦您在执行后退出该方法; 由于您有 @Transactional 注释,因此无法还原更改。
或者,您可以将自动提交设置为 false,并在 A 类的方法 A 中编写一个 try catch 块。如果没有异常,则提交 DB 事务,否则不提交。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.