【问题标题】:Nested @Transactional methods with @Async使用@Async 嵌套@Transactional 方法
【发布时间】:2015-05-29 06:53:51
【问题描述】:

我在 JPA 中使用 Spring。我打开了@EnableAsync@EnableTransactionManagement。在我的用户注册服务方法中,我调用了一些其他的服务方法,它们被注释为@Async。这些方法可以做各种各样的事情,比如发送欢迎电子邮件和使用我们的第三方支付系统注册新创建的用户。

在我想验证第三方支付系统是否成功创建用户之前,一切正常。此时,@Async 方法会尝试创建一个 UserAccount(它引用新生成的 User)并返回 javax.persistence.EntityNotFoundException: Unable to find com.dk.st.model.User with id 2017 错误

注册调用如下所示:

private User registerUser(User newUser, Boolean waitForAccount) {
    String username = newUser.getUsername();
    String email = newUser.getEmail();

    // ... Verify the user doesn't already exist

    // I have tried all manner of flushing and committing right here, nothing works
    newUser = userDAO.merge(newUser);

    // Here is where we register the new user with the payment system.
    // The User we just merged is not /actually/ in the DB
    Future<Customer> newCustomer = paymentService.initializeForNewUser(newUser);
    // Here is where I occasionally (in test methods) pause this thread to wait
    // for the successful account creation.
    if (waitForAccount) {
        try {
            newCustomer.get();
        } catch (Exception e) {
            logger.error("Exception while creating user account!", e);
        }
    }

    // Do some other things that may or may not be @Aysnc

    return newUser;
}

支付服务调用它来完成注册用户的工作,如下所示:

@Async
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Future<Customer> initializeForNewUser(User newUser) {
    // ... Set up customerParams

    Customer newCustomer = null;
    try {
        newCustomer = Customer.create(customerParams);

        UserAccount newAccount = new UserAccount();
        newAccount.setUser(newUser);
        newAccount.setCustomerId(newCustomer.getId());
        newAccount.setStatus(AccountStatus.PRE_TRIAL);

        // When merging, JPA cannot find the newUser object in the DB and complains
        userAccountDAO.merge(newAccount);
    } catch (Exception e) {
        logger.error("Error while creating UserAccount!", e);
        throw e;
    }

    return new AsyncResult<Customer>(newCustomer);
}

列出 here 的 StackOverflow 答案表明我设置了 REQUIRES_NEW 传播,我已经这样做了,但没有这样的运气。

谁能指出我正确的方向?我真的不想直接从我的控制器方法调用 paymentService 。我觉得这肯定是服务级别的调用。

感谢您的帮助!

【问题讨论】:

  • 初读时我会说新创建的用户在支付服务执行之前没有刷新
  • 我也是这么想的。我在 userDAO.merge() 调用中添加了一个 flush() 调用,以便它使用户立即可用,但没有这样的运气。 @Async @Transactional 方法似乎完全在不同的数据库会话中运行。
  • 仅供参考:正是正在发生的事情。 @Async 方法调用在不同的线程上处理(这就是异步的意思),根据定义,这意味着不同的事务。

标签: java spring jpa spring-transactions


【解决方案1】:

在 Vyncent 的帮助下,这是我得出的解决方案。我创建了一个名为UserCreationService 的新类,并将处理User 创建的所有方法都放在该类中。这是一个例子:

@Override
public User registerUserWithProfileData(User newUser, String password, Boolean waitForAccount) {
    newUser.setPassword(password);
    newUser.encodePassword();
    newUser.setJoinDate(Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime());

    User registered = userService.createUser(newUser);
    registered = userService.processNewRegistration(registered, waitForAccount);

    return userService.setProfileInformation(registered);
}

您会注意到此方法上有 NO @Transactional 注释。这是故意的。对应的createUserprocessNewRegistration 定义如下所示:

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public User createUser(User newUser) {
    String username = newUser.getUsername();
    String email = newUser.getEmail();

    if ((username != null) && (userDAO.getUserByUsername(username) != null)) {
        throw new EntityAlreadyExistsException("User already registered: " + username);
    }

    if (userDAO.getUserByUsername(newUser.getEmail()) != null) {
        throw new EntityAlreadyExistsException("User already registered: " + email);
    }

    return userDAO.merge(newUser);
}

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public User processNewRegistration(
        User newUser,
        Boolean waitForAccount) 
{
    Future<UserAccount> customer = paymentService.initializeForNewUser(newUser);
    if (waitForAccount) {
        try {
            customer.get();
        } catch (Exception e) {
            logger.error("Error while creating Customer object!", e);
        }
    }

    // Do some other maintenance type things...

    return newUser;
}

Vyncent 发现事务管理是问题所在。创建其他服务使我能够更好地控制这些事务何时提交。虽然我最初对采用这种方法犹豫不决,但这是与 Spring 托管事务和代理的权衡。

我希望这可以帮助其他人在以后节省一些时间。

【讨论】:

    【解决方案2】:

    尝试创建一个新的 UserService 类来管理用户检查,就像这样

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public User createOrUpdateUser(User newUser) {
        String username = newUser.getUsername();
        String email = newUser.getEmail();
    
        // ... Verify the user doesn't already exist
    
        // I have tried all manner of flushing and committing right here, nothing works
        newUser = userDAO.merge(newUser);
        return newUser;
    }
    

    然后在实际的类中,改变

    private User registerUser(User newUser, Boolean waitForAccount) {
        String username = newUser.getUsername();
        String email = newUser.getEmail();
    
        // ... Verify the user doesn't already exist
    
        // I have tried all manner of flushing and committing right here, nothing works
        newUser = userDAO.merge(newUser);
    

    通过

    private User registerUser(User newUser, Boolean waitForAccount) {
        newUser = userService.createOrUpdateUser(newUser);
    

    带有 @Transactional REQUIRES_NEW 的新用户服务应该强制提交并解决问题。

    【讨论】:

    • 这实际上让我走上了一条道路(以及更深入的理解 - 谢谢!),我相信这意味着这永远不会按预期工作。由于事务 REQUIRES_NEW,创建了一个完全独立的事务,与第一个事务完全隔离。在调用事务中对User 对象调用的任何后续merge 都将尝试插入一个new User(因为它与其他事务隔离)并失败唯一约束。窝。
    • 如果您在 doInTransaction 代码块中编写用户更新,它可能会按您的意愿工作。作为我的儿子,我可以使用笔记本电脑,我举个例子
    • 进一步考虑,我认为如果调用函数做的很少,而是将大量工作委托给 new UserService(我正在调用 UserCreationService )。我明天试试这个,然后报告。感谢您的帮助。
    • 我猜另一个麻烦是调用方法有一个事务打开
    猜你喜欢
    • 2020-08-21
    • 2018-02-26
    • 2011-10-01
    • 2016-10-10
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2014-06-27
    相关资源
    最近更新 更多