@Transactional nested methods with @Async

I am using Spring with JPA. I have included @EnableAsync and @EnableTransactionManagement . In my user registration service method, I have several other service methods that I call annotated @Async . These methods perform various actions, such as sending a welcome letter and registering a new minted user in our third payment system.

Everything works well until I want to verify that the third-party payment system has successfully created the user. At this point, the @Async method @Async trying to create a UserAccount (which refers to the recently minted User ) and errors using javax.persistence.EntityNotFoundException: Unable to find com.dk.st.model.User with id 2017

The register call is as follows:

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

The payment service calls to complete its user registration work and looks like this:

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

The StackOverflow REQUIRES_NEW list below shows that I installed the REQUIRES_NEW distribution that I did, but without that luck.

Can someone point me in the right direction? I really don't want you to call paymentService directly from my controller method. I feel that this should be a service level challenge.

Thanks for any help!

+6
source share
2 answers

Using Vyncent help, here is the solution I came to. I created a new class called UserCreationService and put the whole method that handled the creation of User in this 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 will notice that there is an annotation NO @Transactional in this method. This is special. The corresponding definitions of createUser and processNewRegistration are as follows:

 @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 a stain on the fact that transaction management was a problem. Creating another service allowed me to better control when these transactions take place. While I was unable to take this approach initially, this is a compromise with Spring-driven transactions and proxies.

I hope this helps someone else save some time later.

+3
source

Try creating a new UserService class to control user validation, for example

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

A new userService with @Transactional REQUIRES_NEW should force fix and solve the problem.

+1
source

Source: https://habr.com/ru/post/984234/


All Articles