如何在具有唯一属性的事务中创建实体?

时间:2015-10-22 20:22:09

标签: java google-app-engine google-cloud-datastore objectify

我正在使用客观化。比方说,我有Usernameemail属性。实施注册时,我想检查具有相同名称相同电子邮件的用户是否已注册。由于可以从许多来源调用注册,因此可能会出现竞争条件。

为了防止竞争条件,一切都必须以某种方式包装在交易中。如何消除竞争条件?

GAE文档解释了如果实体不存在但如果他们认为id已知则如何创建实体。因为,我需要检查两个属性,我不能指定id。

3 个答案:

答案 0 :(得分:2)

我可以想到两种可能的解决方案:

使用实体设计:

假设您有一个User @Entity,其电子邮件地址将使用@Id。然后,您创建一个Login @EntityName@IdRef<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_NameUser_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)
"""