Java 线程安全输入验证

Java 线程安全输入验证,java,multithreading,spring,hibernate,Java,Multithreading,Spring,Hibernate,我正在构建一个SpringBoot应用程序,它允许注册、提交各种需要验证的数据等。例如,我有一个带有基本约束的实体设置: @Entity public class Account extends BaseEntity { public static final int MAX_LENGTH = 64; @Column(unique = true, nullable = false, length = MAX_LENGTH) private String usernam

我正在构建一个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;
}
现在,在这种情况下,如果我收到很多多个请求,其中一个成功地通过了验证,那么如果用户名或电子邮件首先被强制为小写,我将遇到一个异常。但是,例如,我希望允许用户使用大写/小写用户名、电子邮件字符等进行注册。基于这个问题,我可以向实体添加一个额外的字段,或者在数据库级别添加一个更复杂的约束,但我希望这样做时不会出现溢出数据和java代码

例如,我收到两个创建新帐户的请求,间隔几毫秒:

Request 1:
username=foo
email=foo@foo.com

Request 2:
username=foO
email=foO@foO.com
在此阶段,我会检查重复项(电子邮件和用户名设置为小写,但在保存时,我会保持大小写不变):

但是,由于请求彼此非常接近,第一个请求可能尚未创建新帐户,因此第二个帐户的检查通过,因此我得到两个数据库条目

因此,实际的问题是,如何在我的服务层中对此类操作执行线程安全验证,而不会对性能造成巨大影响

我为本例选择的解决方案

在设置用户名和电子邮件时,还要将这些值应用于它们的小写对应项,并对它们应用唯一的约束。这样,我就可以为这两个属性保留用户设置的大小写:

@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();
    }

    ...
}

没有数据库的帮助就没有简单的解决方案,因为事务是相互隔离的

另一种方法是将用户名/电子邮件临时存储在内存中,并进行复杂的同步以执行检查。在集群环境中,情况会更加复杂。这非常难看,很难维护(如果单个同步点的锁保持时间过长,可能会显著影响性能)


标准而简单的解决方案是使用数据库约束。

没有数据库的帮助,就没有简单的解决方案,因为事务是相互隔离的

另一种方法是将用户名/电子邮件临时存储在内存中,并进行复杂的同步以执行检查。在集群环境中,情况会更加复杂。这非常难看,很难维护(如果单个同步点的锁保持时间过长,可能会显著影响性能)


标准而简单的解决方案是使用数据库约束。

据我所知,您的所有代码都已经是线程安全的,假设几个
validate*
方法和
DoeSemailorNameExist(…)
也是线程安全的。嗨,我在验证器中看不到任何非线程安全的逻辑。您遇到的问题是什么。您不应该预先验证正在使用的数据库约束layer@Edd每个数据完整性约束都必须由数据库层处理。即使不能以声明方式定义约束,也可以使用事务以编程方式进行定义。@Edd例如,如果需要在没有声明性约束的情况下实现唯一的电子邮件地址,您可以做的是
开始交易->锁定表->检查表中是否存在电子邮件->插入新记录->解锁表->结束交易
。但如果你想保持理智,这应该尽可能靠近数据库。据我所知,你所有的代码都已经是线程安全的,假设几个
validate*
方法和
doeSemailorNameExist(…)
也是线程安全的。嗨,我在验证器中没有看到任何不线程安全的逻辑。您遇到的问题是什么。您不应该预先验证正在使用的数据库约束layer@Edd每个数据完整性约束都必须由数据库层处理。即使不能以声明方式定义约束,也可以使用事务以编程方式进行定义。@Edd例如,如果需要在没有声明性约束的情况下实现唯一的电子邮件地址,您可以做的是
开始交易->锁定表->检查表中是否存在电子邮件->插入新记录->解锁表->结束交易
。但如果你想保持理智,这应该尽可能靠近数据库进行。因此,我猜这个例子的最佳选择是——如果我想让用户允许使用大写/小写用户名和电子邮件创建帐户,那么应该为这些属性存储两个附加字段,并始终强制它们为同一大小写,并对它们执行唯一验证相反,是的,如果你的数据库支持的话,也可以使用某种独特的功能索引。因此,我猜对于这个例子来说,最好的选择是——如果我想让用户允许创建具有大写/小写用户名和电子邮件的帐户,那就是为这些属性存储两个额外的字段,并始终强制它们是相同的大小写并执行独特的操作而是对它们进行验证。是的,或者如果数据库支持,则使用某种唯一的函数索引。
@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();
    }

    ...
}