我正在使用客观化。比方说,我有User
种name
和email
属性。实施注册时,我想检查具有相同名称或相同电子邮件的用户是否已注册。由于可以从许多来源调用注册,因此可能会出现竞争条件。
为了防止竞争条件,一切都必须以某种方式包装在交易中。如何消除竞争条件?
GAE文档解释了如果实体不存在但如果他们认为id已知则如何创建实体。因为,我需要检查两个属性,我不能指定id。
答案 0 :(得分:2)
我可以想到两种可能的解决方案:
使用实体设计:
假设您有一个User
@Entity
,其电子邮件地址将使用@Id
。然后,您创建一个Login
@Entity
,Name
为@Id
,Ref<User>
为User
。现在可以使用可在事务中使用的关键查询来查询两者。这样就不可能有重复。
@Entity
public class User {
@Id
private String email;
}
@Entity
public class Login {
@Id
private String name;
private Ref<User> user;
}
使用索引复合属性:
您可以定义一个包含这两个值的索引复合属性(注意:这只显示索引复合属性的含义,不要像那样实现):
@Entity
public class User {
@Id
private Long id;
private String email;
private String name;
@Index
private String composite;
@OnSave
private onSave(){
composite = email + name;
}
}
但是,正如stickfigure指出的那样,如果在事务中使用索引属性,则无法保证唯一性(事实上,您根本无法通过事务中的索引属性进行查询)。
这是因为在事务中,您只能按键或祖先进行查询。因此,您需要将复合密钥外包到一个单独的@Entity
中,该复合密钥使用复合密钥@Id
。
@Entity
public class UserUX {
// for email OR name: email + name (concatenation of two values)
// for email AND name: email OR name
// (you would create two entities for each user, one with name and one with the email)
@Id
private String composite;
private Ref<User> user;
}
此实体可用于密钥查询,因此可用于事务。
修改强> 如果对此答案发表评论,您希望“限制具有相同电子邮件和姓名的用户”,您也可以使用UserUX实体。您可以使用电子邮件创建一个,并使用名称创建一个。我在上面添加了代码注释。
答案 1 :(得分:2)
受到@ konqi的回答的启发,我提出了类似的解决方案。
我们的想法是创建User_Name
和User_Email
个实体,这些实体将保留到目前为止创建的所有用户的名称和电子邮件。没有父母关系。为方便起见,我们也将保留用户名和电子邮件属性;我们以较少的读/写交易存储。
@Entity
public class User {
@Id public Long id;
@Index public String name;
@Index public String email;
// other properties...
}
@Entity
public class User_Name {
private User_Name() {
}
public User_Name(String name) {
this.name = name;
}
@Id public String name;
}
@Entity
public class User_Email {
private User_Email() {
}
public User_Email(String email) {
this.email = email;
}
@Id public String email;
}
现在通过检查唯一字段在事务中创建用户:
User user = ofy().transact(new Work<User>() {
@Override
public User run()
{
User_Name name = ofy().load().key(Key.create(User_Name.class, data.username)).now();
if (name != null)
return null;
User_Email email = ofy().load().key(Key.create(User_Email.class, data.email)).now();
if (email != null)
return null;
name = new User_Name(data.username);
email = new User_Email(data.email);
ofy().save().entity(name).now();
ofy().save().entity(email).now();
// only if email and name is unique create the user
User user = new User();
user.name = data.username;
user.email = data.email;
// fill other properties...
ofy().save().entity(user).now();
return user;
}
});
这将保证这些属性的唯一性(至少我的测试经验证明了:))。通过不使用Ref<?>
,我们保持数据紧凑,这将导致更少的查询。
如果只有一个唯一属性,最好将其设为主要实体的@Id
。
也可以将用户的@Id
设置为电子邮件或名称,并将新种类的数量减少一个。但我认为为每个唯一属性创建一个新的实体类使得意图(和代码)更加清晰。
答案 2 :(得分:1)
这是来自python sdk,但概念应转换为java
http://webapp-improved.appspot.com/_modules/webapp2_extras/appengine/auth/models.html#Unique
"""A model to store unique values.
The only purpose of this model is to "reserve" values that must be unique
within a given scope, as a workaround because datastore doesn't support
the concept of uniqueness for entity properties.
For example, suppose we have a model `User` with three properties that
must be unique across a given group: `username`, `auth_id` and `email`::
class User(model.Model):
username = model.StringProperty(required=True)
auth_id = model.StringProperty(required=True)
email = model.StringProperty(required=True)
To ensure property uniqueness when creating a new `User`, we first create
`Unique` records for those properties, and if everything goes well we can
save the new `User` record::
@classmethod
def create_user(cls, username, auth_id, email):
# Assemble the unique values for a given class and attribute scope.
uniques = [
'User.username.%s' % username,
'User.auth_id.%s' % auth_id,
'User.email.%s' % email,
]
# Create the unique username, auth_id and email.
success, existing = Unique.create_multi(uniques)
if success:
# The unique values were created, so we can save the user.
user = User(username=username, auth_id=auth_id, email=email)
user.put()
return user
else:
# At least one of the values is not unique.
# Make a list of the property names that failed.
props = [name.split('.', 2)[1] for name in uniques]
raise ValueError('Properties %r are not unique.' % props)
"""