I want to assure that the username is unique when creating a new one or updating one username. I wrote the code below and it works fine for creating and updating the username. But when updating only other user fields, like age or sex, and maintaining the same username, it returns invalid because the username already exists.
It's important to say that the validation I want is using BindingResult. I already have a database which considers username a unique constraint. But now I want the same validation with BindingResult. But the way I did causes error when updating an user.
Defining of the annotation #UniqueUsername
package app.condominio.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = UniqueUsernameValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueUsername {
String message() default "Nome de usuário já está sendo usado";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Implementing the validator
package app.condominio.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import app.condominio.service.UsuarioService;
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
#Autowired
UsuarioService usuarioService;
#Override
public void initialize(UniqueUsername username) {
}
#Override
public boolean isValid(String username, ConstraintValidatorContext cxt) {
return !usuarioService.existe(username);
}
}
In Usuario.class
#Entity
#Table(name = "Usuarios")
public class Usuario implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#UniqueUsername
private String username;
#NotBlank
private String password;
#NotBlank
private String nome;
#Email
private String email;
...
UsuarioService
#Service
#Transactional
public class UsuarioServiceImpl implements UsuarioService {
#Autowired
private UsuarioDao usuarioDao;
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public boolean existe(String username) {
return usuarioDao.existsByUsername(username);
}
UsuarioDao
public interface UsuarioDao extends CrudRepository<Usuario, Long> {
Usuario findByUsername(String username);
Boolean existsByUsername(String username);
}
Controller
#Controller
#RequestMapping("conta")
public class UsuarioController {
#Autowired
private UsuarioService usuarioService;
#GetMapping("/cadastrar/sindico")
public ModelAndView preCadastro(#ModelAttribute("usuario") Usuario usuario, ModelMap model) {
model.addAttribute("conteudo", "cadastrarSindico");
return new ModelAndView("site/layout", model);
}
#PostMapping("/cadastrar/sindico")
public ModelAndView posCadastro(#Valid #ModelAttribute("usuario") Usuario usuario, BindingResult validacao, ModelMap model) {
if (validacao.hasErrors()) {
return preCadastro(usuario, model);
}
usuarioService.salvarSindico(usuario);
model.addAttribute(usuario);
model.addAttribute("conteudo", "cadastrarCondominio");
return new ModelAndView("site/layout", model);
}
I think UniqueUsernameValidator actions as expected.
I am not very clear about your application.So it is difficult to implement an method to to check if validating user have the same id or not of user in database.May be you should provide more details, such as code of method usuarioService.existe(username).
For me, to assure that the username is unique, i do this:
#Column(unique=true)
String username;
or use #UniqueConstraint
#Table(name = "users", uniqueConstraints = {
#UniqueConstraint(columnNames = {
"username"
})
})
but note that it will work if you let JPA create your tables.
#UniqueConstraint
You can try following approach using validation groups:
Create a marker interface (or class)
public interface UserCreateValidationGroup {
}
Then use it in you entity like following
#NotBlank
#UniqueUsername(groups = UserCreateValidationGroup.class)
private String username;
And then use it in Controller along with #Validated (Spring's variant for JSR-303). Note that you'll have to split your single method into separate create and update methods, to distinct validation.
Create:
#PostMapping("/cadastrar/sindico")
public ModelAndView create(
#Validated(UserCreateValidationGroup.class) #ModelAttribute("usuario") Usuario usuario,
BindingResult validacao,
ModelMap model
) { ... }
Update:
// note, here I did use PUT and no argument for #Validated annotation
#PutMapping("/cadastrar/sindico")
public ModelAndView update(
#Validated #ModelAttribute("usuario") Usuario usuario,
BindingResult validacao,
ModelMap model
) { ... }
However, you'll have to perform manual check (UsuarioService.existe) for username in the update case.
Related
I am very new to Spring boot and this is my first spring dummy project where I'm trying to learn to save an array of objects in mysql-db.
Actually, I am trying to use the saveAll method from the crudRepository in my controller. But it's giving me an error no instance(s) of type variable(s) S exist so that Iterable<S> conforms to List<User>
My JSON looks like this:
[
{
"first_name": "Jack",
"last_name": "Jill",
"email": "jacjill#gmail.com",
"password": "passq3623"
},
{
"first_name": "John",
"last_name": "Doe",
"email": "john#gmail.com",
"password": "pass23"
}
]
This is my entity class
package com.example.testbatch.Entities;
import javax.persistence.*;
#Entity
#Table(name = "user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public int id;
public String first_name;
public String last_name;
public String email;
public String password;
}
Here is my User Model Class
package com.example.testbatch.Models;
public class User {
public int id;
public String firstName;
public String lastName;
public String email;
public String password;
}
Here is my repository
package com.example.testbatch.Repositories;
import com.example.testbatch.Entities.UserEntity;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface UserRepository extends CrudRepository<UserEntity, Integer> {
#Override
List<UserEntity> findAll();
}
and here is my rest controller
package com.example.testbatch.Controllers;
import com.example.testbatch.Entities.UserEntity;
import com.example.testbatch.Models.User;
import com.example.testbatch.Repositories.UserRepository;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
#RestController
#RequestMapping("/api/v1/user")
public class UserController {
#Autowired
UserRepository userRepository;
#PostMapping("/register")
public void saveUsers(#RequestBody UserEntity userEntity) {
List<User> persistedUser = userRepository.saveAll(List.of(userEntity));
}
}
Can anyone please help me? This is ery first time I am usig spring boot
You have some problems about your code:
At first you have a DTO (User class), so, you should use this DTO inside your controller in saveUsers method and more importantly you pass an array of objects in your JSON, so you have to use a List not an Object as the parameter:
public void saveUsers(#RequestBody List<User> users) {...}
Another problem is you don't have any getters and setters inside you DTO (public class User) and Entity (public class UserEntity) classes and also you have to add a method inside your DTO class for mapping DTO to Entity:
public class User {
...
public static UserEntity dtoToEntity(User user){
UserEntity userEntity = new UserEntity();
userEntity.setEmail(user.getEmail());
userEntity.setPassword(user.getPassword());
userEntity.setFirst_name(user.getFirstName());
userEntity.setLast_name(user.getLastName());
return userEntity;
}
}
Also you must change your JSON properties to match your DTO's properties:
[
{
"firstName": "Jack",
"lastName": "Jill",
...
},
...
]
As the last one, change your controller saveUsers method as (saveAll method return Iterable not List):
#PostMapping("/register")
public void saveUsers(#RequestBody List<User> users) {
List<UserEntity> userEntities = new ArrayList<>();
for (User user : users) {
userEntities.add(dtoToEntity(user));
}
Iterable<UserEntity> persistedUser = userRepository.saveAll(userEntities);
}
Notice: As a point remember that because you have auto-generated id inside your Entity, so you don't need id property for your DTO, obviously because it's auto-generated and you don't need to get it from input;
The user_repo is returning a List of type UserEntity
while in yout controller you are asking for a List of type User
There is not mapping between UserEntity and User.
I was trying to add custom annotation validation on the age field. as I saw some JSR 303 topics
Controller
#Validated
#Controller
public class StudentController {
#Autowired
private StudentRepository repository;
#PostMapping("/send")
public ResponseEntity saveStudent(#AgeValidator #RequestBody Student student) {
System.out.println("saveStudent invoked");
repository.save(student);
ResponseData responseData = new ResponseData();
responseData.setResultId("result");
responseData.setResultValue("saved");
ResponseEntity entity = new ResponseEntity(responseData, HttpStatus.OK);
return entity;
}
}
Model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#Column(length = 7)
private String Id;
private String fullName;
private Integer age;
private String department;
}
AgeValidator
package com.example.demo4;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = AgeValidatorImpl.class)
public #interface AgeValidator {
String message()
default "Please enter a valid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
AgeValidatorImpl
class AgeValidatorImpl implements ConstraintValidator<AgeValidator, Student> {
#Override
public void initialize(AgeValidator constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation); //To change body of generated methods, choose Tools | Templates.
}
#Override
public boolean isValid(Student t, ConstraintValidatorContext cvc) {
System.out.println("AgeValidatorImpl invoked");
if (t.getAge() < 18) {
return false;
} else if (t.getAge() > 40) {
return false;
}
return true;
}
}
so if am sending using postman at any age it saves the record and it's not validating. I saw many peoples commented to add annotation on controller #validated which I import from import org.springframework.validation.annotation.Validated. Still Why this is not working. what am missing?
You can try this,
I have updated #Age annotation. You can provide upper and lower limit for validation. Note I that have added ElementType.FIELD to #Target. It allows you to use this in class fields as well.
#Documented
#Target({ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = AgeValidator.class)
public #interface Age {
int lower() default 14;
int upper() default 60;
String message() default "Please enter a valid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is the validation constrain for the annotation.
public class AgeValidator implements ConstraintValidator<Age, Integer> {
private int upperLimit;
private int lowerLimit;
#Override
public boolean isValid(Integer i, ConstraintValidatorContext constraintValidatorContext) {
return lowerLimit < i && i < upperLimit;
}
#Override
public void initialize(Age constraintAnnotation) {
this.lowerLimit = constraintAnnotation.lower();
this.upperLimit = constraintAnnotation.upper();
}
}
You can pass the annotation to class fields and override the upper and lower limit.
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#Column(length = 7)
private String Id;
private String fullName;
#Age(lower = 10, upper = 70)
private Integer age;
private String department;
}
Used #Validated annotation to validate the Student object against all the validation constraints.
#PostMapping("/send")
public ResponseEntity saveStudent(#Validated #RequestBody Student student)
Update
Replace this dependency,
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
<type>jar</type>
</dependency>
by this
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I have a problem with Spring, in particular with ConstraintValidator. I want to release custom validation for field contains email. It must be unique. OK. The task is clear, I do it like this:
UniqueEmail.java
#Constraint(validatedBy = UniqueEmailValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueEmail {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueEmailValidator.java
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
private final UserRepository userRepository;
public UniqueEmailValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return email != null && !userRepository.findByEmail(email).isPresent();
}
}
User.java
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
#NotEmpty(message = "Email should not be empty")
#Email(message = "Email should be valid")
#UniqueEmail(message = "Email address is already registered")
private String email;
#NotEmpty(message = "Password should not be empty")
private String password;
#NotEmpty(message = "Name should not be empty")
#Size(min = 2, max = 30, message = "Name should be between 2 and 30 characters")
private String username;
private boolean enabled = true;
#Enumerated(value = EnumType.STRING)
private Role role;
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
AuthController.java
#Controller
#RequestMapping("/auth")
public class AuthController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public AuthController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#GetMapping("/login")
public String login(Principal principal) {
if(principal != null)
return "redirect:/";
return "auth/login";
}
#GetMapping("/register")
public String register(#ModelAttribute("user") User user, Principal principal) {
if(principal != null)
return "redirect:/";
return "auth/register";
}
#PostMapping("/register")
public String newCustomer(Principal principal, Model model, #ModelAttribute("user") #Valid User user, BindingResult bindingResult) {
if(principal != null)
return "redirect:/";
if(bindingResult.hasErrors())
return "auth/register";
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole(Role.CUSTOMER);
userRepository.saveAndFlush(user);
model.addAttribute("success", true);
model.addAttribute("user", new User());
return "auth/register";
}
}
If i try input existing email everything works fine (got message "Email address is already registered"). But if I try input a new email, I get an error "Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: bla.bla.bla.validator.UniqueEmailValidator.] with root cause".
I'm trying do with #Component and #Autowired, but got the same result.
I'm trying do with noArgs constructor and got NullPointerException (UserRepository not injected).
Why? I don'n understand.
The error above is because your UniqueEmailValidator would have an empty constructor.
See also: javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator
The userRepository you can inject into your Constraint Validator using #Autowired annotation, as stated:
here: Inject Spring Beans into Annotation-based Bean Validator
and here: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#validation-beanvalidation-spring-constraints
Please pay also attention to put #Valid annotation before User user in all related controller methods
Solved the problem in this way. I understand that this is a crutch, but I have not yet found another solution.
#Component
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
private UserRepository userRepository;
public UniqueEmailValidator() {
}
#Autowired
public UniqueEmailValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if(userRepository == null)
return true;
return email != null && !userRepository.findByEmail(email).isPresent();
}
}
Added noArgs Constructor, and in isValid function added ckeck if null userRepository.
I have following problem. This is my entity:
package application.model;
import lombok.Data;
import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
#NamedQueries({
#NamedQuery(name = User.GET_USERS, query = User.QUERY_GET_USERS),
#NamedQuery(name = User.VERIFY_CREDENTIALS, query = User.QUERY_VERIFY_CREDENTIALS),
#NamedQuery(name = User.GET_USER_ROLE, query = User.QUERY_GET_USER_ROLE),
#NamedQuery(name = User.GET_USER_ID_BY_EMAIL, query = User.QUERY_GET_USER_ID_BY_EMAIL)
})
#Data
#Entity
#Table(name = "users")
public class User {
public static final String GET_USERS = "User.get_users";
public static final String QUERY_GET_USERS = "select u from User u";
public static final String VERIFY_CREDENTIALS = "User.verify_credentials";
public static final String QUERY_VERIFY_CREDENTIALS = "select u from User u where u.email = :email and u.password = :password";
public static final String GET_USER_ROLE = "User.get_role";
public static final String QUERY_GET_USER_ROLE = "select u.roles from User u where u.id= :id";
public static final String GET_USER_ID_BY_EMAIL = "User.get_userId_by_mail";
public static final String QUERY_GET_USER_ID_BY_EMAIL = "select u from User u where u.email= :email";
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
public int id;
#NotEmpty(message="provide firstname")
#Size(min=4,message = "minimum 4 chars")
#Column(name="firstname",nullable = false)
private String firstname;
#NotEmpty(message="provide lastname")
#Size(min=4,message = "minimum 4 chars")
#Column(name="lastname",nullable = false)
private String lastname;
#NotEmpty(message="provide mail")
#Email(message = "mail should be valid")
#Column(name="email",unique = true,nullable = false)
private String email;
#NotEmpty(message="provide pass")
#Size(min=4,message = "minimum 4 chars")
#Column(name="password",nullable = false)
private String password;
#ManyToMany
#JoinTable(name="user_roles")
private List<Role> roles;
}
dao layer:
package application.dao;
import application.model.Role;
import application.model.User;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
#Repository
public class UserDAOImpl implements UserDAO {
#PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager em;
#Transactional
#Override
public User addUser(User user) {
return em.merge(user);
}
#Override
public User addCustomerRole(User user) {
List<Role> roles= new ArrayList<>();
roles.add(em.find(Role.class,3));
user.setRoles(roles);
return user;
}
#Override
public User addSellerRole(User user) {
List<Role> roles= new ArrayList<>();
roles.add(em.find(Role.class,2));
user.setRoles(roles);
return user;
}
}
service layer:
package application.service;
import application.controller.CommonAPIController;
import application.dao.UserDAO;
import application.model.User;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
#Service
public class UserServiceImpl implements UserService {
final static Logger LOGGER = Logger.getLogger(UserServiceImpl.class.getName());
private final UserDAO userDAO;
public UserServiceImpl(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public User addCustomer(User user) {
return userDAO.addCustomerRole(userDAO.addUser(user));
}
}
and controller:
package application.controller;
import application.model.User;
import application.service.UserService;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RestController
public class CustomerAPIController {
private final UserService userService;
public CustomerAPIController(UserService userService) {
this.userService = userService;
}
#PostMapping(value = "/customer/register")
public User addCustomer(#RequestBody #Valid User user){
return userService.addCustomer(user);
}
}
I want to validate field in my User entity, I add validation constraints to Entity and #Valid annotation in controller - next to #RequestBody.
I used this tutorial how to do it: https://mkyong.com/spring-boot/spring-rest-validation-example/
After build I still can send json to /customer/register with payload with empty fields:
{
"firstname": "",
"lastname": "",
"email": "",
"password": "pass"
}
User is successfully registered, although annotations in User entity should not allow to do this?
Do you see what i'm doing wrong?
My project is Spring, in tutorial we have SpringBoot, there's a difference?
Also i have a dao and service layer between entity and controller and i using lombok's #Data annotation.
These details make a difference here or is the error elsewhere?
try to add BindingResults like this
#PostMapping(value = "/customer/register")
public User addCustomer(#RequestBody #valid BindingResults result,User user){
if(results.hasErrors()){
return some error page;
}
else{
return userService.addCustomer(user);
}
Ensure that your controller class is annotated with #Validated
I want to validate two fields of a Request Class in manner that Either one field is valid OR another field is valid.
Eg:
Request Bean
public class CarRequest {
#NotEmpty
private String customerName;
#NotEmpty
private String customerId;
Controller Method
public #ResponseBody CarResponse addCar(
#ModelAttribute #Valid CarRequest request, BindingResult results)
throws RuntimeException, ValidationException {
if (results.hasErrors()) {
LOG.error("error occured while adding the car");
throw new ValidationException(
"Error Occoured while validiating car request");
}
}
Here I want to check that either customerName should be NotEmpty OR customerId should be NotEmpty. then my validation should pass. How can I implement it . Please suggest!!
You need to create custom validator to validate multiple fields.
create a custom annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = CarRequestValidator.class)
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestAnnotation {
String message() default "{RequestAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
create a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CarRequestValidator implements
ConstraintValidator<RequestAnnotation, CarRequest> {
#Override
public void initialize(RequestAnnotation constraintAnnotation) {
}
#Override
public boolean isValid(CarRequest value, ConstraintValidatorContext context) {
// validation logic goes here
return false;
}
}
Now, annotate your model with custom annotation:
#RequestAnnotation
public class CarRequest {
private String customerName;
private String customerId;
}