Reputation: 421
What is the best way to check by validator to don't have duplicate ID when you do a POST?
I tried to create a custom validator but I get always an Internal Server Error
{ "timestamp": "2020-04-25T14:37:19.158+0000", "status": 500, "error": "Internal Server Error", "message": "HV000030: No validator could be found for constraint 'com.omega.mtest.validator.IdConstraint' validating type 'java.lang.Integer'. Check configuration for 'id'", "path": "/user" }
This is my model class
public class User {
@IdConstraint
@Min(value = 1, message = "ID can't be zero or null")
@Max(value = 1000000, message = "We collect a billion records")
private int id;
@Pattern(regexp = "^[a-zA-Z ]*$", message = "Input doesn't match for a full name")
private String name;
@Min(value = 10)
@Max(value = 120, message = "We didn't expect that age")
private int age;
@Pattern(regexp = "^[a-zA-Z ]*$", message = "Input doesn't match for a city name")
private String city;
public User(int id, String name, int age, String city) {
this.id = id;
this.name = name;
this.age = age;
this.city = city;
}
//getters
}
Custom Validator Interface:
@Constraint(validatedBy = UserValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RUNTIME)
public @interface IdConstraint {
String message() default "The input list cannot contain two ugual IDs";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Class implementation of the interface
public class UserValidator implements ConstraintValidator<IdConstraint, List<User>> {
@Override
public void initialize(IdConstraint constraintAnnotation) {
}
@Override
public boolean isValid(List<User> users, ConstraintValidatorContext context) {
if (users.size() == 1) {
return true;
} else {
for (int i = 0; i < users.size(); i++) {
for (int j = 0; j < users.size(); j++) {
if (i != j) {
if (users.get(i).getId() == users.get(j).getId()) {
return true;
}
}
}
}
return false;
}
}
Controller class:
@Validated
@RestController
public class UsersController {
@Autowired
public UserService userService;
@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
return new ResponseEntity<>(userService.createUser(user), HttpStatus.CREATED);
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("Not valid due to validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
@RequestMapping(value = "/users", method = RequestMethod.GET)
public List<User> getUsers() {
return userService.getAllUsers();
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public User getUserById(@PathVariable int id) {
return userService.findUserById(id);
}
}
UserService
public interface UserService {
List<User> getAllUsers();
User createUser(User user);
User findUserById(int id);
List<User> list();
}
Repo
@Component
public class UserRepository implements UserService {
private List<User> users;
public UserRepository() {
users = new ArrayList<>();
}
@Override
public List<User> getAllUsers() {
return users.stream().collect(Collectors.toList());
}
@Override
public User createUser(User user) {
users.add(user);
return user;
}
@Override
public User findUserById(int id) {
return users.stream()
.filter(t -> t.getId() == id)
.findAny()
.orElse(null);
}
@Override
public List<User> list() {
return users;
}
}
Upvotes: 1
Views: 1380
Reputation: 421
I did this way, and it's working:
@RequestMapping(value = "/user", method = RequestMethod.POST)
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
Optional<User> check = userService.findUserById(user.getId());
if (check.isPresent()) {
return new ResponseEntity<>(userService.createUser(user), HttpStatus.CREATED);
}
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public ResponseEntity<User> getUserById(@PathVariable int id) {
if (userService.findUserById(id).isPresent()) {
return new ResponseEntity<>(userService.findUserById(id).get(), HttpStatus.OK);
}
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
Upvotes: 0
Reputation: 3304
Your validator signature is not correct.
You're not validate list of users here but an Integer .
Like this
public class UserIdValidator implements ConstraintValidator<IdConstraint, Integer> {
Then you have to ask your service if the id is already used.
public class UserIdValidator implements ConstraintValidator<IdConstraint, Integer> {
@Autowired
Private UserService userService;
@Override
public void initialize(IdConstraint constraintAnnotation) {
}
@Override
public boolean isValid(Integer id, ConstraintValidatorContext context) {
return !userService.existsById(id);
}
}
Upvotes: 1
Reputation: 18440
First of all you are using @IdConstraint
in the wrong place means on private int id
. You have to put this on List<User>
. And your validator work when you have list of user and any on two user don't have same id in request body.
You can solve this 2 way.
Checking is service:
Here in post endpoint you are using one user as a request body. So you can check if id exists in the database in service using getById()
of the repository. If exist then raise exception otherwise insert.
User user= userRepository.getById(user.getId());
if (ObjectUtils.isEmpty(user)) {
throw new UserNotFoundException();
}
Using Validator:
It is not a better approach to call repository in validator function. But still, you want you can do like this.
public class UserIdValidator implements ConstraintValidator<IdConstraint, Integer> {
@Autowired
Private UserService userService;
@Override
public void initialize(IdConstraint constraintAnnotation) {
}
@Override
public boolean isValid(Integer id, ConstraintValidatorContext context) {
User user= userService.findUserById(id);
if (ObjectUtils.isEmpty(user)) {
return false;
}
return true;
}
}
Upvotes: 0