javax.validation.ConstraintViolationException, но данные присутствуют - PullRequest
0 голосов
/ 20 апреля 2020

Я пытаюсь сохранить сущность User, используя Spring Data JPA в проекте Spring boot Web (1.5.4). Некоторые поля сущности User имеют аннотации @NotEmpty, @NotNull для проверки. Поэтому, когда я сохраняю User с заполненными данными, гибернация сообщает мне, что поля, отмеченные @NotEmpty, @NotNull, требуют данных. Но как это возможно? Данные существуют! Я проверил это в режиме отладки.

Затем я двинулся вперед и отлаживал источники гибернации. Оказалось, что метод org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(..) был вызван из org.hibernate.action.internal.EntityIdentityInsertAction.preInsert() 2 раза Первый раз с данными внутри User, второй - со значениями null внутри User. Во второй раз поднялся ConstraintViolationException

Вопрос в том, почему это происходит и как это исправить?

User сущность

@Data
@Entity
@Table( name = "user",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = "username"),
                @UniqueConstraint(columnNames = "email")
        })
@Where(clause = "deleted=false")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @NotEmpty(message = "First name is required")
    private String firstName;

    @NotEmpty(message = "Last name is required")
    private String lastName;

    @NotEmpty(message = "Email is required")
    @Email(message = "Email is invalid")
    @Column(unique=true)
    private String email;

    @NotNull
    @NotEmpty(message = "UserName is required")
    @Size(min = 4, max = 30)
    private String username;

    @Column(name="enabled", nullable = false, columnDefinition = "int default 1")
    private int enable = 1;

    @NotNull(message="Password invalid")
    @NotEmpty(message = "Password is required")
    @Size(max=60)
    private String password;

    @Lob
    private String summary;

    @ManyToOne(optional = false)
    @JoinColumn(name = "rol_id")
    @NotNull
    private Rol role;

    /*
    some other fields without @Notnull and @NotEmpty, but with @OneToMany (@JoinTable), @ManyToOne (@JoinColumn) relations 

   getters and setters, generated by lombok
*/
}

Role

@Entity
@Data
@Where(clause = "deleted=false")
public class Rol implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;

    /*
        getters and setters generated by lombok
    */

}

UserRepository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    User findByUsername(String username);

    User findByEmail(String email);

    User findUserByUsernameAndIdIsNot(String username, Long id);

    User findById(Long id);

    List<User>findAll();

    Boolean existsByUsernameAndIdIsNot(String username, Long id);

    Boolean existsByEmailAndIdIsNot(String email, Long id);
}

JSON тело в запросе POST отображается посредством maptruct от DTO к пользователю JSON request

{
  "id": 0,
  "name": "",
  "firstName": "Potato",
  "lastName": "test 1",
  "email": "potato1@gmail.com",
  "username": "potato",
  "enable": 1,
  "password": "12345678",
  "photoURL": "12345678",
  "summary": "",
  "role": {
    "id": 4
  }

}

Response

{
"timestamp": 1587355579867,
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [com.domain.MyProject.Entities.User] during persist time for groups [javax.validation.groups.Default, ] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='UserName is required', propertyPath=username, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='UserName is required'} ConstraintViolationImpl{interpolatedMessage='Password is required', propertyPath=password, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='Password is required'} ConstraintViolationImpl{interpolatedMessage='Password invalid', propertyPath=password, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='Password invalid'} ConstraintViolationImpl{interpolatedMessage='Last name is required', propertyPath=lastName, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='Last name is required'} ConstraintViolationImpl{interpolatedMessage='First name is required', propertyPath=firstName, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='First name is required'} ConstraintViolationImpl{interpolatedMessage='Email is required', propertyPath=email, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='Email is required'} ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=role, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='{javax.validation.constraints.NotNull.message}'} ConstraintViolationImpl{interpolatedMessage='may not be null', propertyPath=username, rootBeanClass=class com.domain.MyProject.Entities.User, messageTemplate='{javax.validation.constraints.NotNull.message}'} ]",
"path": "/api/user/save"
}

Список используемых технологий:

  • Spring Boot Starter Web 1.5.4 Release
  • Spring Starter data Выпуск jpa 1.5.4 (1.11.4 Release inside )
  • hibernate-core 5.0.12.Final
  • hibernate-entitymanager 5.0.12.Final
  • hibernate-validator 5.3.5.Final
  • Spring Security 4.2.3 Release
  • Embedded Tomcat
  • mapstruct 1.3.0.Beta2
  • Lombok
  • Gradle 4.4.1 с оберткой 2.7
  • more link

Кто-нибудь знает, как решить эту странную проблему?

ps переход на более новую версию пружинной загрузки и данные о пружине не вариант на данный момент, так как проект имеет обширную функциональность, уже реализованную на основе этих версий весны

ОБНОВЛЕНО: UserController

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private UserServices userService;

    @Autowired
    private UserRepository userRepository;

     @PostMapping("/save")
    public  ResponseEntity<MessageResponse> saveUser(@Valid @RequestBody UserViewModel user){

        if (userRepository.existsByUsernameAndIdIsNot(user.getUsername(), user.getId())) {
            return ResponseEntity
                    .badRequest()
                    .body(MessageResponse.of("Error: Username is already taken!"));
        }

        if (userRepository.existsByEmailAndIdIsNot(user.getEmail(), user.getId())) {
            return ResponseEntity
                    .badRequest()
                    .body(MessageResponse.of("Error: Email is already in use!"));
        }

        Pair<String, User> result = userService.saveUser(user);
        if(result.getSecond() == null)
            return new ResponseEntity<>(MessageResponse.of(result.getFirst()), HttpStatus.CONFLICT);

        return  new ResponseEntity<>(MessageResponse.of(result.getFirst()), HttpStatus.CREATED);
    }
}

UserServices

@Service
@Transactional
public class UserServices {
    @Autowired
   private UserRepository UserRepository;
    @Autowired
    private RolServices rolServices;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    //...

    @Autowired
    private UserRepository userRepository;

    private UserMapper mapper
            = Mappers.getMapper(UserMapper.class);

    public Pair<String, User> saveUser(UserViewModel vm){
        if (vm.getId() > 0L) {
            return updateUser(vm);
        } else {
            User savedUser = userRepository.findByUsername(vm.getUsername());

            if ( savedUser != null){
                return Pair.of("The user you are trying to save already exist", null);
            }
            User resultUser = mapper.mapToEntity(vm);
            resultUser.setPassword(bCryptPasswordEncoder.encode(resultUser.getPassword()));
            if (resultUser.getRole().getId() == Roles.ROLE_STUDENT.ordinal() || resultUser.getRole().getId() == Roles.ROLE_FREE_STUDENT.ordinal()){
                resultUser.setName(resultUser.getFirstName() + ' ' + resultUser.getLastName());
            }
            User result = userRepository.save(resultUser);
            if (result == null){
                return Pair.of("Couldn't save the user", null);
            } else {
                return  Pair.of("User saved successfully", result);
            }
        }
    }
}
...