当前位置: 首页 > 知识库问答 >
问题:

嵌套@Transactional方法和@Async

衡子安
2023-03-14

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

一切都很好,直到我想验证第三方支付系统是否成功创建了用户。此时,@Async方法试图创建一个UserAccount(引用新创建的User),并出现javax.persistence.EntityNotFoundException:无法找到ID为2017的com.dk.st.model.用户

寄存器调用如下所示:

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答案表明,我设置了一个新的传播,我已经完成了,但没有这样的运气。

有人能给我指出正确的方向吗?我真的不想直接从我的控制器方法调用paymentService。我觉得这肯定应该是一个服务级别的调用。

谢谢你的帮助!

共有2个答案

壤驷俊逸
2023-03-14

尝试创建一个新的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);

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

时才俊
2023-03-14

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

您会注意到,这个方法上没有事务注释。这是故意的。相应的createUser和processNewRegistration定义如下所示:

@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托管事务和代理的权衡。

我希望这能帮助其他人节省一些时间。

 类似资料:
  • 问题内容: 是否可以在spring嵌套@Transactional带注释的方法?考虑这样的事情: 如果我在b()中回滚并在a()中回滚,在这种情况下会发生什么? 问题答案: 不需要 对方法的第二个注释,因为默认情况下 ,其传播为,因此由method调用的方法将是事务性的。如果要在通过method调用的方法中启动新事务,则需要修改传播规则。阅读有关交易传播的信息。

  • 我有一个dao类(),它用annotaion(在类级别)标记,没有其他参数。在这个dao类中,我有一个方法,在某些情况下,它需要抛出一个已检查的异常并执行事务回滚。类似这样的事情: 这个很好用。但是,这个dao方法是从一个服务方法调用的,该服务方法也被标记为@transactional: 问题是,当从调用并将事务标记为仅回滚时,我会得到一个。 Spring创建了两个事务拦截器:一个用于,一个用于。

  • 我有一个相当典型的场景,其中有一个main@实体,他内部的所有内容都是可嵌入的(因此,没有父实体,内部的所有内容都没有意义)。现在JPA 2.0阻止我在另一个@ElementCollection中定义的@Embeddeble中嵌套一个@ElementCollection: JSR-317 2.6可嵌入类和基本类型的集合包含在元素集合中的可嵌入类(包括另一个可嵌入类中的可嵌入类)不得包含元素集合,也

  • 问题内容: 在执行以下操作时,当我尝试访问延迟加载的异常时遇到错误: 我的实体看起来像这样: 我的印象是,通过在我的方法中使用@Transactional批注,它将使会话保持活动状态,直到该方法完成为止,从而使我可以访问延迟加载的Address集合。 我想念什么吗? 编辑 按照Tomasz的建议:在我的方法中,状态变为。 这确实可以缩小问题的范围,但是为什么此时我的“交易”不能处于活动状态? 问题

  • 问题内容: 我想在应用程序开始时阅读文本数据装置(CSV文件),并将其放入数据库中。 为此,我创建了带有初始化方法(@PostConstruct批注)的PopulationService。 我也希望它们在单个事务中执行,因此我在同一方法上添加了@Transactional。 然而,@Transactional似乎被忽略:该交易启动/停止我的低水平DAO方法。 那我需要手动管理交易吗? 问题答案: