Я пытаюсь сохранить сущность 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);
}
}
}
}