为什么发生RuntimeException时事务不回滚?

Why transactional does not rollback when RuntimeException occur?

我想在服务层中测试一个非事务性方法,而内部方法是单独事务性的。我想知道如果某些方法抛出异常会发生什么。它是否正确回滚?我也试过 rollbackFor 但没有用。有什么建议吗?

app.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create-drop

server.error.include-message=always
server.error.include-binding-errors=always

控制器

@PostMapping("/test")
    public void test(){
        service.test();
    }

服务

@Service
public class UserService {
  @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;


    public void test() {
        User user1 = new User();
        String encodedPassword = passwordEncoder.encode("123");
        user1.setUsername("username");
        user1.setPassword(encodedPassword);
        user1.setFirst_name("name1");
        user1.setLast_name("family1");
        save(user1);
        User update = update(user1.getUsername());
        throw new RuntimeException();// I expect Spring rollback all data for save and update methods
//but it seems data has been committed before an exception occur.
    }

 @Transactional
    public void save(User user) {
        if (userExists(user.getUsername()))
            throw new ApiRequestException("Username has already taken!");
        else {
            User user1 = new User();
            String encodedPassword = passwordEncoder.encode(user.getPassword());
            user1.setUsername(user.getUsername());
            user1.setPassword(encodedPassword);
            user1.setFirst_name(user.getFirst_name());
            user1.setLast_name(user.getLast_name());
            user1.setCreate_date(user.getCreate_date());
            user1.setModified_date(user.getModified_date());
            user1.setCompany(user.getCompany());
            user1.setAddresses(user.getAddresses());
            userRepository.save(user1);
          //  throw new RuntimeException(); Similarly I expect rollback here.
        }
    }

    @Transactional
    public User update(String username) {
        User userFromDb = userRepository.findByUsername(username);
        userFromDb.setFirst_name("new username");
        userFromDb.setLast_name("new lastname");
        return userRepository.save(userFromDb);
    }

}

首先,我不确定您是否理解交易代表什么。如果有一个工作单元包含多个要执行的功能,并且您希望它们完全失败或完全通过 - 那就是您使用事务的时候。

所以在 test() 方法的情况下 - 为什么底层机制(在本例中是 Hibernate)关心回滚 save()update() 中发生的事情,当 test() 本身不是交易?

很抱歉问的比回答的多,但据我所知 - 您需要用注释而不是单个方法来注释 test()。或者你可以把它们都注释掉。

由于 spring 代理的工作方式 默认情况下 ,您不能从实例中调用“代理”方法。

考虑一下,当您在方法上放置 @Transactional 注释时,Spring 会创建该 class 的代理,而代理是处理事务 begin/commit/rollback 的地方。代理调用实际的 class 实例。 Spring 对你隐藏了这个。

但是鉴于此,如果您在 class 实例 (test()) 上有一个方法,它会调用自身的另一个方法 (this.save()),则该调用不会进行通过代理,所以没有“@Transactional”代理。 RuntimeException发生时无事回滚

有一些方法可以改变 Spring 代理的方式,这将使它起作用,但多年来它已经发生了变化。有多种选择。一种方法是创建单独的 classes。也许是 UserServiceHelper。 UserServiceHelper 包含由 UserService 调用的@Transactional 方法。当需要不同的@Transactional 隔离和传播时,也会出现同样的问题。

相关答案和信息:

  • Spring @Transaction method call by the method within the same class, does not work?
  • Does Spring @Transactional attribute work on a private method?
  • Spring Transaction Doesn't Rollback

您的示例代码对于您要执行的操作不是很清楚。通常,@Transactional 将放在服务 class 上并应用于所有 public 方法(如 test())。