[英]@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.