如何在交易中创建具有独特属性的实体?
How can I create an entity in a transaction with unique properties?
我正在使用对象化。比如说,我有一个 User
类型,具有 name
和 email
属性。实施注册时,我想检查是否已注册具有相同名称 或 相同电子邮件的用户。因为可以从许多来源调用注册,所以可能会出现竞争条件。
为了防止竞争条件,一切都必须以某种方式包装在事务中。如何消除竞争条件?
GAE 文档解释了如何在实体不存在的情况下创建实体,但它们假定 ID 已知。因为,我需要检查两个无法指定 id 的属性。
这来自 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)
"""
我可以想到两种可能的解决方案:
使用实体设计:
假设您有一个 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
中,该 @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;
}
此实体可用于关键查询,因此可用于交易。
编辑:
如果正如对此答案的评论,您希望 'restricts users with same email and name' 您也可以使用 UserUX 实体。您可以用电子邮件创建一个,用名字创建一个。我在上面添加了代码注释。
受@konqi 回答的启发,我想出了一个类似的解决方案。
我们的想法是创建 User_Name
和 User_Email
实体,以保留迄今为止创建的所有用户的姓名和电子邮件。不会有父子关系。为了方便起见,我们也将保留用户的姓名和电子邮件属性;我们以更少的价格交易存储 read/write。
@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<?>
s,我们保持数据紧凑,从而减少查询。
如果只有一个独特的属性,最好将其@Id
作为主要实体。
也可以将用户的@Id
设置为email或者name,新的种类减少一个。但我认为为每个独特的 属性 创建一个新的实体类型会使意图(和代码)更加清晰。
我正在使用对象化。比如说,我有一个 User
类型,具有 name
和 email
属性。实施注册时,我想检查是否已注册具有相同名称 或 相同电子邮件的用户。因为可以从许多来源调用注册,所以可能会出现竞争条件。
为了防止竞争条件,一切都必须以某种方式包装在事务中。如何消除竞争条件?
GAE 文档解释了如何在实体不存在的情况下创建实体,但它们假定 ID 已知。因为,我需要检查两个无法指定 id 的属性。
这来自 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)
"""
我可以想到两种可能的解决方案:
使用实体设计:
假设您有一个 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
中,该 @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;
}
此实体可用于关键查询,因此可用于交易。
编辑: 如果正如对此答案的评论,您希望 'restricts users with same email and name' 您也可以使用 UserUX 实体。您可以用电子邮件创建一个,用名字创建一个。我在上面添加了代码注释。
受@konqi 回答的启发,我想出了一个类似的解决方案。
我们的想法是创建 User_Name
和 User_Email
实体,以保留迄今为止创建的所有用户的姓名和电子邮件。不会有父子关系。为了方便起见,我们也将保留用户的姓名和电子邮件属性;我们以更少的价格交易存储 read/write。
@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<?>
s,我们保持数据紧凑,从而减少查询。
如果只有一个独特的属性,最好将其@Id
作为主要实体。
也可以将用户的@Id
设置为email或者name,新的种类减少一个。但我认为为每个独特的 属性 创建一个新的实体类型会使意图(和代码)更加清晰。