I'm using Spring with JPA. I have @EnableAsync
and @EnableTransactionManagement
turned on. In my user registration service method, I have a few other service methods I call that are annotated @Async
. These methods do various things like sending a welcome email and registering the newly minted user with our third party payment system.
Everything works well until I want to verify that the third party payment system successfully created the user. At that point, the @Async
method attempts to create a UserAccount
(that references the newly minted User
) and errors out with a javax.persistence.EntityNotFoundException: Unable to find com.dk.st.model.User with id 2017
The register call looks like this:
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 out to do its work of registering the user 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 answer listed here suggests that I set a REQUIRES_NEW
propagation, which I have done, but with no such luck.
Can anyone point me in the right direction? I really don't want to have to call the paymentService directly from my controller method. I feel that it should be a service level call for sure.
Thanks for any help!
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 handledUser
creation in that class. Here is an example:You'll notice that there is NO
@Transactional
annotation on this method. This is on purpose. The correspondingcreateUser
andprocessNewRegistration
definitions look like this: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.
Make a try by creating a new UserService class to manage user check, like so
then in the actual class, change
by
The new userService with @Transactional REQUIRES_NEW should force the commit and solve the issue.