问题
I'm using Google App Engine and Datastore with objectify.
I'm trying to create an account for a user (Google user), so I need to check if that users exist, and if not, create an account for that user, but I'm facing the fact that sometimes the account is created twice if I spam the createAccount API method
@ApiMethod(name = "account.google.create")
public Account createGoogleAccount(final User user) throws OAuthRequestException {
if (user == null) {
throw new OAuthRequestException("createAccount: OAuthRequestException<User is not authenticated>");
}
Account alreadyExisting = RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now();
if (alreadyExisting != null) {
throw new OAuthRequestException("createAccount: OAuthRequestException<Account already exist>");
}
return RObjectifyService.getObjectify().transactNew(new Work<Account>() {
@Override
public Account run() {
Account account = AccountProvider.createAccountFromGoogleProvider(user);
RObjectifyService.save(account);
return account;
}
});
}
I read that I should use transactions but I can't because if I do this in the transaction:
RObjectifyService.getObjectify().load().type(Account.class).filter("accountId.GOOGLE", user.getUserId()).filter("email", user.getEmail()).first().now()
I get an error "Only ancestor queries are allowed inside transactions", but I don't see another way to do it
Is this the right way to do it?
Thanks
回答1:
You need a transaction and you need an entity whose primary key is the value you are trying to make unique (ie the username).
The pattern is a little tricky. There is some discussion of it here. The basic idea in pseudocode is:
- start transaction
- load entity with the unique PK
- if there is an entity
- abort and return duplicate error
- else
- create the entity with the unique PK (+ whatever extra work you need)
- commit the transaction
- if the commit fails
- abort and return duplicate error
- else
- everything is great!
You probably don't want to make username
the primary key of your User
entity, so create a separate Username
entity and mix in creation of Username
with User
in the same transaction. Be sure to leave the Username
entity around; that's what guarantees uniqueness.
This problem (uniqueness) is actually one of the more technically challenging problems in a massively distributed system like the GAE datastore. It's simple to solve in a traditional RDBMS only if the traditional RDBMS is a single-master system, with the resulting impact on scalability and fault tolerance. GAE gives you the necessary primitives to enforce clusterwide uniqueness; they just aren't super easy to use.
回答2:
The best way in this case for have strong consistency is retrieve the entity by key.
来源:https://stackoverflow.com/questions/28552875/objectify-handle-race-condition-to-prevent-duplicate-account-creation