Nested @Transactional methods with @Async

后端 未结 2 1125
一个人的身影
一个人的身影 2021-02-07 13:44

I\'m using Spring with JPA. I have @EnableAsync and @EnableTransactionManagement turned on. In my user registration service method, I have a few other

相关标签:
2条回答
  • 2021-02-07 13:59

    Make a try by creating a new UserService class to manage user check, like so

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

    then in the actual class, change

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

    by

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

    The new userService with @Transactional REQUIRES_NEW should force the commit and solve the issue.

    0 讨论(0)
  • 2021-02-07 14:01

    With Vyncent's help, here is the solution that I arrived at. I created a new class called UserCreationService and put all of the method that handled User creation in that class. Here is an example:

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

    You'll notice that there is NO @Transactional annotation on this method. This is on purpose. The corresponding createUser and processNewRegistration definitions look like this:

    @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 was spot on that transaction management was the issue. Creating the other service allowed me to have better control over when those transactions committed. While I was hesitant to take this approach initially, that's the tradeoff with Spring managed transactions and proxies.

    I hope this helps someone else save some time later.

    0 讨论(0)
提交回复
热议问题