gpt4 book ai didi

java - @Transactional 服务方法回滚 hibernate 更改

转载 作者:行者123 更新时间:2023-12-01 11:13:47 26 4
gpt4 key购买 nike

我在 @Service 类中有一个方法,它在两个不同的 @Service 类中调用两个不同的方法。这两种不同的方法在数据库中保存两个实体(通过 hibernate ),它们都可能抛出一些异常。
我希望如果抛出异常,独立于哪个 @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

如果可以的话,这是我的主要类(class):
@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”更改了表引擎类型。我所做的是这样的:
  • 我在 application.properties 文件中添加了这个属性,以便告诉 JPA 它应该用来“操作”表的引擎类型:

  • spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect


  • 我删除了数据库中的所有现有表(使用错误的引擎类型),并让 JPA 使用正确的引擎(InnoDB)重新创建所有表。

  • 现在抛出的所有 RuntimeExceptions 使事务回滚其中所做的所有更改。

    警告:我注意到,如果抛出不是 RuntimeException 子类的异常,则不会应用回滚,并且所有已经完成的更改都保留在数据库中。

    最佳答案

    您要实现的目标应该是开箱即用的。检查您的 Spring 配置。

    确保您创建了 TransactionManager bean 并确保您放置了 @EnableTransactionManagement 关于你的一些 Spring 的注释 @Configuration s。该注解负责注册必要的 Spring 组件,这些组件为注解驱动的事务管理提供支持,例如 TransactionInterceptor以及基于代理或基于 AspectJ 的建议,在 @Transactional 时将拦截器编织到调用堆栈中方法被调用。

    请参阅链接的文档。

    如果您正在使用 spring-boot应该automatically如果您有 PlatformTransactionManager,请为您添加此注释类路径上的类。

    另外,请注意 已检查异常不会触发事务回滚 .只有运行时异常和错误才会触发回滚。当然,您可以使用 rollbackFor 配置此行为。和 noRollbackFor注释参数。

    编辑

    当您澄清您正在使用 spring-boot 时,答案是:一切都应该在没有任何配置的情况下工作。

    这是 spring-boot 版本的最小 100% 工作示例 2.1.3.RELEASE (但应该适用于任何版本的c):

    依赖项:

        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 中没有找到任何书籍或用户。事务回滚。如果删除 throw new RuntimeException("User not saved")来自 UserService 的线路,两个实体都将被很好地保存。

    您还应该看到 org.springframework.transaction 的日志包裹在 TRACE级别,例如,您将看到:
    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 定义的规则将用于确定是否应回滚事务。这些规则是:

    Rolls back on runtime, but not checked, exceptions by default.


    RuntimeException是运行时异常,因此事务将被回滚。

    线路 Clearing transaction synchronization是实际应用回滚的地方。您会看到其他一些 Applying rules to determine whether transaction should rollback消息因为 @Transactional方法嵌套在这里( UserService.saveUserCompositeService.saveUserAndBook 调用,两个方法都是 @Transactional ),但它们所做的只是确定 future 操作的规则(在事务同步点)。实际回滚将只执行一次,在最外层 @Transactional方法退出。

    关于java - @Transactional 服务方法回滚 hibernate 更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56201933/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com