java - Thread safe input validation -
i'm building spring boot application allows registering, submitting of various data , etc requires validation. example have entity setup basic constraints:
@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; ... }
each time before creating new account, validation performed on service layer:
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; } }
the validator snippet:
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 query on users see if no such email or username // exists. before checking email , username set // lowercase characters on service layer. if (accountservice.doesemailorusernameexist(email, username)) { return false; } return true; }
now in case if lot of multiple requests , 1 of them manages past validation, encounter exception if username or email forced lowercase in first place. example want allow users register upper case/lower case username, email characters , etc. based on this question add additional field entity or add more complex constraint on database level, want without overflow data , in java code.
so example these 2 requests create new account milliseconds apart:
request 1: username=foo email=foo@foo.com request 2: username=foo email=foo@foo.com
during phase check duplicates (email , username set lower case, when saving keep case intact):
if (accountservice.doesemailorusernameexist(email, username)) { return false; }
however since requests close each other, first request might not have created new account yet check second account passes , 2 database entries.
so actual question be, how perform thread safe validation these kinds of actions in service layer without huge performance impact?
solution i've chosen example
when setting username , email, apply values lowercase counterparts , apply unique constraints on them well. way keep user set case 2 properties:
@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(); } ... }
there no simple solution without of database because transactions isolated each other.
the alternative temporarily store usernames/emails in memory , complex synchronizations perform checks. things more complicated in clustered environments. ugly , hard maintain (and may impact performance if lock single synchronization point held long).
the standard , straightforward solution use database constraints.
Comments
Post a Comment