java线程安全输入验证
我正在构建一个SpringBoot应用程序,它允许注册、提交各种需要验证的数据等。例如,我有一个带有基本约束的实体设置:
@Entity
public class Account extends BaseEntity {
public static final int MAX_LENGTH = 64;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private String username;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private String email;
...
}
每次创建新帐户之前,都会在服务层上执行验证:
public Account register(String username, String email, String rawPassword) {
if (!accountValidator.validate(username, email, rawPassword)) {
return null;
}
Account account = new Account(username, email, passwordEncoder.encode(rawPassword));
try {
return accountRepository.save(account);
} catch (DataIntegrityViolationException e) {
return null;
}
}
验证程序代码段:
public boolean validate(String username, String email, String password) {
if (!validateUsernameStructure(username)) {
return false;
}
if (!validateEmailStructure(email)) {
return false;
}
if (!validatePasswordStructure(password)) {
return false;
}
// Performs a query on all users to see if no such email or username
// already exists. Before checking the email and username are set to
// lowercase characters on the service layer.
if (accountService.doesEmailOrUsernameExist(email, username)) {
return false;
}
return true;
}
现在,在这种情况下,如果我收到很多多个请求,其中一个成功地通过了验证,那么如果用户名或电子邮件首先被强制为小写,我将遇到一个异常。但是,例如,我希望允许用户使用大写/小写用户名、电子邮件字符等进行注册。基于this问题,我可以在实体中添加一个附加字段,或者在数据库级别添加一个更复杂的约束,但是我希望这样做时不会出现溢出数据和java代码
例如,我收到两个创建新帐户的请求,间隔几毫秒:
Request 1:
username=foo
email=foo@foo.com
Request 2:
username=foO
email=foO@foO.com
在此阶段,我会检查重复项(电子邮件和用户名设置为小写,但在保存时,我会保持大小写不变):
if (accountService.doesEmailOrUsernameExist(email, username)) {
return false;
}
但是,由于请求彼此非常接近,第一个请求可能尚未创建新帐户,因此第二个帐户的检查通过,因此我得到两个数据库条目
因此,实际的问题是,如何在我的服务层中对此类操作执行线程安全验证,而不会对性能造成巨大影响
我为本例选择的解决方案
在设置用户名和电子邮件时,还要将这些值应用于它们的小写对应项,并对它们应用唯一的约束。这样,我就可以为这两个属性保留用户设置的大小写:
@Entity
public class Account extends BaseEntity {
public static final int MAX_LENGTH = 64;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private final String username;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private String email;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private final String normalizedUsername;
@Column(unique = true, nullable = false, length = MAX_LENGTH)
private String normalizedEmail;
public Account(String username, String email) {
this.username = username;
this.email = email;
this.normalizedUsername = username.toLowerCase();
this.normalizedEmail = email.toLowerCase();
}
...
}
# 1 楼答案
没有数据库的帮助就没有简单的解决方案,因为事务是相互隔离的
另一种方法是将用户名/电子邮件临时存储在内存中,并进行复杂的同步以执行检查。在集群环境中,情况会更加复杂。这非常难看,很难维护(如果单个同步点的锁保持时间过长,可能会显著影响性能)
标准且简单的解决方案是使用数据库约束