I\'m using Spring with JPA. I have @EnableAsync
and @EnableTransactionManagement
turned on. In my user registration service method, I have a few other
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.
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.