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);
}
リストされているStackOverflowの回答 ここ は、私が行ったREQUIRES_NEW
伝播を設定したことを示唆していますが、そのような運はありません。
誰かが私を正しい方向に向けることができますか?コントローラーメソッドから直接paymentServiceを呼び出す必要は本当にありません。確かにサービスレベルの呼びかけだと思います。
助けてくれてありがとう!
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);
}
このメソッドには[〜#〜] 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が管理するトランザクションとプロキシとのトレードオフです。
これが他の誰かが後で時間を節約するのに役立つことを願っています。
新しいserServiceクラスを作成して、ユーザーチェックを管理してみてください。
@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);
沿って
private User registerUser(User newUser, Boolean waitForAccount) {
newUser = userService.createOrUpdateUser(newUser);
@ Transactional REQUIRES_NEWの新しいuserServiceは、コミットを強制し、問題を解決する必要があります。