Java 带有@Async的嵌套@Transactional方法
我在JPA中使用Spring。我已启用了Java 带有@Async的嵌套@Transactional方法,java,spring,jpa,spring-transactions,Java,Spring,Jpa,Spring Transactions,我在JPA中使用Spring。我已启用了@EnableAsync和@EnableTransactionManagement。在我的用户注册服务方法中,我调用了一些其他的服务方法,它们被注释为@Async。这些方法可以做很多事情,比如发送欢迎电子邮件,在我们的第三方支付系统中注册新用户 在我想验证第三方支付系统是否成功创建了用户之前,一切正常。此时,@Async方法尝试创建一个UserAccount(它引用新创建的用户)并出错为javax.persistence.EntityNotFoundExc
@EnableAsync
和@EnableTransactionManagement
。在我的用户注册服务方法中,我调用了一些其他的服务方法,它们被注释为@Async
。这些方法可以做很多事情,比如发送欢迎电子邮件,在我们的第三方支付系统中注册新用户
在我想验证第三方支付系统是否成功创建了用户之前,一切正常。此时,@Async
方法尝试创建一个UserAccount
(它引用新创建的用户
)并出错为javax.persistence.EntityNotFoundException:找不到id为2017的com.dk.st.model.User
register调用如下所示:
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);
}
@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;
}
private User registerUser(User newUser,Boolean waitForAccount){
字符串username=newUser.getUsername();
字符串email=newUser.getEmail();
//…验证用户是否不存在
//我在这里尝试了各种各样的洗脸和承诺,没有任何效果
newUser=userDAO.merge(newUser);
//这里是我们向支付系统注册新用户的地方。
//我们刚刚合并的用户不在数据库中
Future newCustomer=paymentService.initializeForNewUser(新用户);
//这里是我偶尔(在测试方法中)暂停此线程以等待的地方
//为了成功创建帐户。
如果(waitForAccount){
试一试{
newCustomer.get();
}捕获(例外e){
logger.error(“创建用户帐户时出现异常!”,e);
}
}
//做一些其他事情,可能是,也可能不是@Aysnc
返回新用户;
}
支付服务调用来完成注册用户的工作,如下所示:
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);
}
@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;
}
@Async
@凌驾
@事务性(传播=传播。需要\u新建)
公共未来初始值FornewUser(用户newUser){
//…设置customerParams
客户newCustomer=null;
试一试{
newCustomer=Customer.create(customerParams);
UserAccount newAccount=newuseraccount();
newAccount.setUser(newUser);
newAccount.setCustomerId(newCustomer.getId());
newAccount.setStatus(AccountStatus.PRE_-TRIAL);
//合并时,JPA在DB中找不到newUser对象并发出投诉
userAccountDAO.merge(newAccount);
}捕获(例外e){
logger.error(“创建用户帐户时出错!”,e);
投掷e;
}
返回新结果(newCustomer);
}
列出的StackOverflow答案表明我设置了一个需要新的传播,我已经这样做了,但没有这样的运气
谁能给我指出正确的方向吗?我真的不想直接从控制器方法调用paymentService。我觉得这应该是一个服务级别的电话
谢谢你的帮助 尝试创建一个新的UserService类来管理用户检查,如下所示
@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;
}
然后在实际的课堂上,改变
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);
借
带有@Transactional REQUIRES的新用户服务\u new应该强制提交并解决问题。在Vyncent的帮助下,我找到了一个解决方案。我创建了一个名为UserCreationService
的新类,并将处理User
创建的所有方法都放在该类中。以下是一个例子:
@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);
}
您会注意到,在这个方法上没有@Transactional
注释。这是故意的。相应的createUser
和processNewRegistration
定义如下所示:
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);
}
@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;
}
@覆盖
@事务性(传播=传播。需要\u新建)
公共用户createUser(用户newUser){
字符串username=newUser.getUsername();
字符串email=newUser.getEmail();
if((username!=null)&&(userDAO.getUserByUsername(username)!=null)){
抛出新EntityAlreadyExistsException(“用户已注册:+用户名”);
}
if(userDAO.getUserByUsername(newUser.getEmail())!=null){
抛出新EntityAlreadyExistsException(“用户已注册:+电子邮件”);
}
返回userDAO.merge(newUser);
}
@凌驾
@事务性(传播=传播。需要\u新建)
公共用户进程新注册(
用户newUser,
布尔值waitForAccount)
{
未来客户=paymentService.initializeForNewUser(新用户);
如果(waitForAccount){
试一试{
customer.get();
}捕获(例外e){
logger.error(“创建客户对象时出错!”,e);
}
}
//做一些其他维护类型的事情。。。
返回新用户;
}
Vyncent指出交易管理是一个问题。创建其他服务使我能够更好地控制这些事务何时提交。虽然我最初对采用这种方法犹豫不决,但这是与Spring管理的事务和代理的权衡
我希望这有助于其他人以后节省一些时间。首先阅读时,我会说新创建的用户在执行支付服务之前没有刷新。我也这么认为。我在userDAO.merge()调用中添加了一个flush()调用,这样用户就可以立即使用了,但没有这样的运气。似乎@Async
@Transactional
方法正在完全不同的DB会话中运行。仅供参考:这正是发生的情况。@Async methodcall是在不同的线程上处理的(这就是Async的意思),根据定义,这意味着一个不同的事务。这实际上让我走上了一条道路(以及更深层次的理解——谢谢!),我认为这意味着这永远不会像预期的那样工作。由于事务需要_NEW,因此会创建一个与第一个事务完全隔离的完全独立的事务。对t中的User
对象调用的任何后续merge