Reputation: 287400
First of all, I'm not taking about the primary id of the record. I'm talking about an field that is used by users to identify the record that's automatically generated but changeable by the user, not sequential and not a UUID. For example, starting with an account entity:
@Entity
@Data
class Account {
@Id
@GeneratedValue
private int id;
@Column(unique=true)
@NotNull
private String slug;
@Column
private String name;
}
and then I simply create a record:
@Autowired
private AccountRepository accountRepository;
Account account = new Account();
account.setName("ACME");
accountRepository.saveAndFlush(account);
At that point, the slug should have been generated, either completely randomly, or by doing something based on the name. How should that be done?
I know without locking the whole table it's impossible to ensure that the insertion won't result in an exception due to the uniqueness constrain being violated. I'm actually OK blocking the whole table or even letting the exception happen (you need a lot of requests per second fora conflict to happen between the check for availability and the insert).
Upvotes: 1
Views: 4482
Reputation: 368
I will do something like a separate Bean, helper or service class like this.
public class SlugService {
public String generateSlug(String slug)
{
if (accountRepo.getBySlug(slug) != null){ //check if it is already
return slug
} else {
slug.append("-"); //whatever the syntax
generateSlug();
}
}
public String makeSlug()
{
String slug = split by " ", replace by "_"(accountObject.getName);
generateSlug(slug)
}
}
Call the makeSlug(); method.
Upvotes: 0
Reputation: 73528
If you separate the slug from the Account
table and put it in a (id, slug)
table by itself, you can generate the slug first (retrying until you succeed) and then persist the Account
with a link to the just generated slug id.
You can't achieve this in a @PrePersist
method, so your service needs to create the slug whenever you're creating an new Account
. However it does simplify things on the application side (e.g. you don't need to wonder which constraint was violated when persisting an Account
).
Depending on your other code, you can also get around locking the Account
table and even the Slug
table if you go for the optimistic approach.
A pseudo-code example of a service method that creates a new account (providing new Slug()
creates the random slug):
@Autowired SlugRepository slugRepository;
@Autowired AccountRepository accountRepository;
public void createAccount(Account a) {
Slug s = null;
while(s == null) {
try {
s = slugRepository.save(new Slug());
} catch(Exception e) {
}
}
a.setSlug(s);
accountRepository.save(a);
}
Upvotes: 1
Reputation: 2152
I can think of JPA callbacks to generate the slug. In your case @PrePersist
could be useful.
That said, why you need to make sure the value is available with a select before inserting the record, so the window for a collision to occur is tiny? You do have unique constraint on the column, right?
Update
Personally I would prefer to address it like this:
@PrePersist
when generating the the slug. Use to random UUID or timestamp to minimise the possibility of collision. No checking for collision as chances are minimal.Account
for user generated slug, always check first using query for collision. This check will offcourse happen in service update method itself. This way I can be DB agnostic and also don't have to use repository/service in entity or listener classes.
Upvotes: 0