带有@Async 的嵌套@Transactional 方法
Nested @Transactional methods with @Async
我正在使用 Spring 和 JPA。我打开了 @EnableAsync
和 @EnableTransactionManagement
。在我的用户注册服务方法中,我调用了一些其他的服务方法,这些方法被注释为@Async
。这些方法执行各种操作,例如发送欢迎电子邮件和使用我们的第三方支付系统注册新创建的用户。
一切正常,直到我要验证第三方支付系统是否成功创建了用户。那时,@Async
方法尝试创建一个 UserAccount
(引用新创建的 User
)并返回错误 javax.persistence.EntityNotFoundException: Unable to find com.dk.st.model.User with id 2017
注册调用如下所示:
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);
}
列出的 Whosebug 答案 here 建议我设置 REQUIRES_NEW
传播,我已经这样做了,但没有这样的运气。
谁能指出我正确的方向?我真的不想直接从我的控制器方法调用 paymentService。我觉得这肯定是一个服务级别的电话。
感谢您的帮助!
尝试创建一个新的 UserService class 来管理用户检查,就像这样
@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;
}
然后在实际class中,更改
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);
带有 @Transactional REQUIRES_NEW 的新 userService 应该强制提交并解决问题。
在 Vyncent 的帮助下,这是我得出的解决方案。我创建了一个名为 UserCreationService
的新 class,并将所有处理 User
创建的方法放入该 class 中。这是一个例子:
@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);
}
您会注意到此方法上有 NO @Transactional
注释。这是故意的。相应的 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 和 JPA。我打开了 @EnableAsync
和 @EnableTransactionManagement
。在我的用户注册服务方法中,我调用了一些其他的服务方法,这些方法被注释为@Async
。这些方法执行各种操作,例如发送欢迎电子邮件和使用我们的第三方支付系统注册新创建的用户。
一切正常,直到我要验证第三方支付系统是否成功创建了用户。那时,@Async
方法尝试创建一个 UserAccount
(引用新创建的 User
)并返回错误 javax.persistence.EntityNotFoundException: Unable to find com.dk.st.model.User with id 2017
注册调用如下所示:
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);
}
列出的 Whosebug 答案 here 建议我设置 REQUIRES_NEW
传播,我已经这样做了,但没有这样的运气。
谁能指出我正确的方向?我真的不想直接从我的控制器方法调用 paymentService。我觉得这肯定是一个服务级别的电话。
感谢您的帮助!
尝试创建一个新的 UserService class 来管理用户检查,就像这样
@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;
}
然后在实际class中,更改
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);
带有 @Transactional REQUIRES_NEW 的新 userService 应该强制提交并解决问题。
在 Vyncent 的帮助下,这是我得出的解决方案。我创建了一个名为 UserCreationService
的新 class,并将所有处理 User
创建的方法放入该 class 中。这是一个例子:
@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);
}
您会注意到此方法上有 NO @Transactional
注释。这是故意的。相应的 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 托管交易和代理的权衡。
我希望这可以帮助其他人节省一些时间。