将用户存储在具有唯一电子邮件地址的数据存储中
Storing users in datastore with unique email addresses
所以我在 Java 从事一个项目,但实际上语言在这里并不重要。
所以我想在数据存储中创建和存储用户,并且我正在尝试找到执行此操作的最佳方法,这样我就可以确保电子邮件不会被多次使用。因此,在关系 ACID 数据库上执行此操作的正常方法是在事务期间。
锁定数据库,查找电子邮件是否存在,如果存在则解锁并失败,否则插入并解锁。
现在这个概念可以在 Appengine 中使用,您也可以使用事务。但是,由于该条目可能仅在几毫秒前插入,因此由于强一致性/最终一致性,它可能还不存在于数据存储中。
所以我想到的事情:
为所有用户使用全局父级,这样我就可以在我的事务中进行祖先查询,从而强制它成为查询的最新数据。但是,这会导致每秒 1 个 XG 更新的限制出现问题。 (这是我决定不采用的方法)
将插入到 memcache 中的电子邮件存储在单独的列表中,因为即使它被清除,它也可能不会在条目被插入到数据存储之前被清除,所以然后我们可以搜索缓存和数据存储,如果两者都不存在,我们可以假设它不会在数据存储中。但是,此内存缓存查找不会成为事务的一部分,因此仍然存在问题
所以我的主要问题是这些方法都不会使用祖先查询,因此不能作为事务的一部分完成。
谢谢
编辑:
在考虑了结构之后,我正在考虑这样的事情。待会儿回家再测试一下,如果有效,我会把它标记为我接受的答案。
UserBean
@id Long id;
//All child elements will use UserBean as their parent
Login
@id String id; //This will be the a hashed/base64 version of the email address
@Parent UserBean user;
String emailAddress
String hashedPassword;
start transaction
Login login = ofy()
.load()
.type(Login.class)
.key(hashEmail(emailAddress)).now();
if (login == null) {
fail transaction - email already in use
}
Insert UserBean and Login objects into datastore
我使用 App Engine 的 Python 风格,但我怀疑 Java 中有类似的功能。
您可以执行以下操作:
- 使用电子邮件地址作为实体的
key_name
,并且
- 使用
get_or_insert(key_name)
(docs) 创建实体。
通过使用 get_or_insert
,您保证不会多次创建同一个实体。
所以我有一个可行的解决方案,我很乐意在这里分享。
我的两个 POJO:
@Entity
public class UserAccount {
@Id Long _id;
public UserAccount(){
}
public Long get_id() {
return _id;
}
}
@Entity
public class LoginBean {
@Id String emailHash;
//I don't make this an actual @Parent because this would affect the Id
Ref<UserAccount> parent;
String email;
String hashedPassword;
public LoginBean(){
}
public LoginBean(String emailHash, Ref<UserAccount> parent, String email, String hashedPassword){
this.parent = parent;
this.emailHash = emailHash;
this.email = email;
this.hashedPassword = hashedPassword;
}
//All the rest of the getters and setters you want
}
然后在我的实用程序中 class :
final String emailHash = getEmailHash(email);
final String passwordHash = getPasswordHash(password);
UserAccount savedUser = ofy().transact(new Work<UserAccount>() {
public UserAccount run() {
if (lookupByEmail(email) != null) {
return null;
}
UserAccount user = new UserAccount();
Key<UserAccount> userKey = ofy().save().entity(user).now();
LoginBean login = new LoginBean(emailHash, Ref.create(userKey), email, passwordHash);
ofy().save().entity(login).now();
return user;
}
});
再往下:
public LoginBean lookupByEmail(String email) {
String emailhash = getEmailHash(email);
LoginBean r = ofy().load().type(LoginBean .class).id(emailhash).now();
return r;
}
所以我在 Java 从事一个项目,但实际上语言在这里并不重要。
所以我想在数据存储中创建和存储用户,并且我正在尝试找到执行此操作的最佳方法,这样我就可以确保电子邮件不会被多次使用。因此,在关系 ACID 数据库上执行此操作的正常方法是在事务期间。 锁定数据库,查找电子邮件是否存在,如果存在则解锁并失败,否则插入并解锁。
现在这个概念可以在 Appengine 中使用,您也可以使用事务。但是,由于该条目可能仅在几毫秒前插入,因此由于强一致性/最终一致性,它可能还不存在于数据存储中。
所以我想到的事情:
为所有用户使用全局父级,这样我就可以在我的事务中进行祖先查询,从而强制它成为查询的最新数据。但是,这会导致每秒 1 个 XG 更新的限制出现问题。 (这是我决定不采用的方法)
将插入到 memcache 中的电子邮件存储在单独的列表中,因为即使它被清除,它也可能不会在条目被插入到数据存储之前被清除,所以然后我们可以搜索缓存和数据存储,如果两者都不存在,我们可以假设它不会在数据存储中。但是,此内存缓存查找不会成为事务的一部分,因此仍然存在问题
所以我的主要问题是这些方法都不会使用祖先查询,因此不能作为事务的一部分完成。
谢谢
编辑: 在考虑了结构之后,我正在考虑这样的事情。待会儿回家再测试一下,如果有效,我会把它标记为我接受的答案。
UserBean
@id Long id;
//All child elements will use UserBean as their parent
Login
@id String id; //This will be the a hashed/base64 version of the email address
@Parent UserBean user;
String emailAddress
String hashedPassword;
start transaction
Login login = ofy()
.load()
.type(Login.class)
.key(hashEmail(emailAddress)).now();
if (login == null) {
fail transaction - email already in use
}
Insert UserBean and Login objects into datastore
我使用 App Engine 的 Python 风格,但我怀疑 Java 中有类似的功能。
您可以执行以下操作:
- 使用电子邮件地址作为实体的
key_name
,并且 - 使用
get_or_insert(key_name)
(docs) 创建实体。
通过使用 get_or_insert
,您保证不会多次创建同一个实体。
所以我有一个可行的解决方案,我很乐意在这里分享。
我的两个 POJO:
@Entity
public class UserAccount {
@Id Long _id;
public UserAccount(){
}
public Long get_id() {
return _id;
}
}
@Entity
public class LoginBean {
@Id String emailHash;
//I don't make this an actual @Parent because this would affect the Id
Ref<UserAccount> parent;
String email;
String hashedPassword;
public LoginBean(){
}
public LoginBean(String emailHash, Ref<UserAccount> parent, String email, String hashedPassword){
this.parent = parent;
this.emailHash = emailHash;
this.email = email;
this.hashedPassword = hashedPassword;
}
//All the rest of the getters and setters you want
}
然后在我的实用程序中 class :
final String emailHash = getEmailHash(email);
final String passwordHash = getPasswordHash(password);
UserAccount savedUser = ofy().transact(new Work<UserAccount>() {
public UserAccount run() {
if (lookupByEmail(email) != null) {
return null;
}
UserAccount user = new UserAccount();
Key<UserAccount> userKey = ofy().save().entity(user).now();
LoginBean login = new LoginBean(emailHash, Ref.create(userKey), email, passwordHash);
ofy().save().entity(login).now();
return user;
}
});
再往下:
public LoginBean lookupByEmail(String email) {
String emailhash = getEmailHash(email);
LoginBean r = ofy().load().type(LoginBean .class).id(emailhash).now();
return r;
}