I have a spring application and I added to my user entity unique constraints for username and email. So users can't create many accounts with the same username or email. When someone creates a new user with a username that already exists my app throws me ConstraintViolationException
My exception details:
org.postgresql.util.PSQLException: ERROR: Duplicate key values violate unique constraints "ukdfui7gxngrgwn9ewee3ogtgym" Detail: The key (username) = (dev123) already exists.
My user entity:
@Table(name = "usr", uniqueConstraints={
@UniqueConstraint(columnNames ={"username"}),
@UniqueConstraint(columnNames={"email"})})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="usr_seq")
@SequenceGenerator(name = "usr_seq", sequenceName = "usr_seq", initialValue = 1, allocationSize=1)
@Column(name="id")
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "role")
private Role role;
@Column(name = "email")
private String email;
@Column(name = "status")
private Status status;
// @OneToMany(mappedBy = "author")
// private List<Message> messages;
// @OneToMany(mappedBy = "author")
// private List<Comment> comments;
@OneToOne
@JoinColumn(name = "gender_id")
private Gender gender;
@OneToOne
@JoinColumn(name = "address_id")
private Address address;
@ManyToMany
@JoinTable(
name = "user_subscriptions",
joinColumns = @JoinColumn(name = "channel_id") ,
inverseJoinColumns = @JoinColumn(name = "subscriber_id"))
private Set<User> subscribers;
@ManyToMany
@JoinTable(
name = "user_subscriptions",
joinColumns = @JoinColumn(name = "subscriber_id") ,
inverseJoinColumns = @JoinColumn(name = "channel_id"))
private Set<User> subscriptions;
@OneToOne
@JoinColumn(name = "file_id")
private FileEntity fileEntity;
}
My user table:
create table usr
(
id bigint not null
constraint usr_pkey
primary key,
email varchar(255)
constraint ukg9l96r670qkidthshajdtxrqf
unique,
first_name varchar(255),
last_name varchar(255),
password varchar(255),
role integer,
status integer,
username varchar(255)
constraint ukdfui7gxngrgwn9ewee3ogtgym
unique,
address_id integer
constraint fkilsqnqkb7dlk6s5gqedb6lk3r
references address,
file_id bigint
constraint fkabswqn807logqymtak5wfktfr
references file,
gender_id integer
constraint fkp89wdnbeu22hjl41g38rk8a1q
references gender
);
alter table usr
owner to postgres;
i tried to handle that exception in @RestControllerAdvice class:
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(){
ExceptionDetails exceptionDetails = new ExceptionDetails(Errors.ERROR9.getMessage(),timestamp);
return new ResponseEntity<>(exceptionDetails,HttpStatus.FORBIDDEN);
}
But this catches all the problems with the constraints(not null violation, primary key violation, and so on) and I just want to catch specific constraint violations for my username and email
CodePudding user response:
You can not do custom exceptions if you depend on the Hibernate Validator.
To explain why, look at the BeanValidationEventHandler which listens to all Create/Update/Delete events, it uses the Validator
validate
method and deals with only ConstraintViolation
:
private <T> void validate(T object, EntityMode mode, EntityPersister persister,
SessionFactoryImplementor sessionFactory, GroupsPerOperation.Operation operation) {
if ( object == null || mode != EntityMode.POJO ) {
return;
}
TraversableResolver tr = new HibernateTraversableResolver(
persister, associationsPerEntityPersister, sessionFactory
);
Validator validator = factory.usingContext()
.traversableResolver( tr )
.getValidator();
final Class<?>[] groups = groupsPerOperation.get( operation );
if ( groups.length > 0 ) {
final Set<ConstraintViolation<T>> constraintViolations = validator.validate( object, groups );
if ( constraintViolations.size() > 0 ) {
Set<ConstraintViolation<?>> propagatedViolations =
new HashSet<ConstraintViolation<?>>( constraintViolations.size() );
Set<String> classNames = new HashSet<String>();
for ( ConstraintViolation<?> violation : constraintViolations ) {
LOG.trace( violation );
propagatedViolations.add( violation );
classNames.add( violation.getLeafBean().getClass().getName() );
}
StringBuilder builder = new StringBuilder();
builder.append( "Validation failed for classes " );
builder.append( classNames );
builder.append( " during " );
builder.append( operation.getName() );
builder.append( " time for groups " );
builder.append( toString( groups ) );
builder.append( "\nList of constraint violations:[\n" );
for (ConstraintViolation<?> violation : constraintViolations) {
builder.append( "\t" ).append( violation.toString() ).append("\n");
}
builder.append( "]" );
throw new ConstraintViolationException(
builder.toString(), propagatedViolations
);
}
}
}
The Validator
interface deals with ConstraintViolation also:
public interface Validator {
/**
* Validates all constraints on {@code object}.
*
* @param object object to validate
* @param groups the group or list of groups targeted for validation (defaults to
* {@link Default})
* @return constraint violations or an empty set if none
* @throws IllegalArgumentException if object is {@code null}
* or if {@code null} is passed to the varargs groups
* @throws ValidationException if a non recoverable error happens
* during the validation process
*/
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
CodePudding user response:
Before saving the User you can check whether the given username already exists or not. If it already exists then don't save the user and ask to enter another username else save the user.