具有@Async的嵌套@Transactional方法

2022-09-03 14:36:38

我正在使用Spring和JPA。我已经打开了。在我的用户注册服务方法中,我调用了其他一些带注释的服务方法。这些方法可以执行各种操作,例如发送欢迎电子邮件以及使用我们的第三方支付系统注册新用户。@EnableAsync@EnableTransactionManagement@Async

一切正常,直到我想验证第三方支付系统是否成功创建了用户。此时,该方法尝试创建一个(引用新铸造的)并错误@AsyncUserAccountUserjavax.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);
}

这里列出的StackOverflow答案建议我设置一个传播,我已经这样做了,但没有这样的运气。REQUIRES_NEW

任何人都可以为我指出正确的方向吗?我真的不想直接从我的控制器方法调用支付服务。我觉得这肯定是一个服务水平要求。

感谢您的任何帮助!


答案 1

在Vyncent的帮助下,这是我找到的解决方案。我创建了一个名为的新类,并将处理创建的所有方法都放在该类中。下面是一个示例:UserCreationServiceUser

@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);
}

您会注意到此方法没有注释。这是故意的。相应的和定义如下所示:@TransactionalcreateUserprocessNewRegistration

@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的新用户服务应强制提交并解决问题。


推荐