Hibernate validator causing many to many relations

2019-07-22 04:41发布

问题:

I have a ManyToMany relationship between User and Role. I have a custom hibernate validation constraint on my roles Set in User.

In a @PostConstruct I save the initial roles (ADMIN, USER) to the database using standard JpaRepository from spring-data-jpa. I then create an initial user using the admin role.

If I do not have my custom validation, the association is saved correctly and I see an entry in user_role join table. If I have the validation, the user is inserted into the user table, but without an entry into user_role table. The returned entity has the role in the roles set, but it is not saved into the DB. The code is summarized below. I cannot understand how using the RoleRepo to fetch all of the roles could in any way break the save, but it does.

class User {
  @Id
  String username;

  @ValidOption
  @ManyToMany(cascade = {CascadeType.ALL //for example}, fetch=FetchType.EAGER)
  Set<Role> roles;
}

class Role {
  @Id
  String name;
}

class CustomValidator implements ConstraintValidator<ValidOption, Object> {
  RoleRepository roleRepo; //injected by spring... have spring factory

  @Override
  public boolean isValid(Object value, ConstraintValidatorContext context){
    roleRepo.findAll() //<-------------- THIS CALL BREAKS THE SAVE
    return true;
  }
}

@Component
class UserCreator {
  RoleRepository roleRepo;
  UserRepo userRepo;

  @PostConstruct
  void setup(){
    Role admin = roleRepo.saveAndFlush(new Role('ADMIN'));
    roleRepo.saveAndFlush(new Role('USER'));

    User user = new User('admin', Collections.singleton(admin));
    userRepo.save(user); //<------ DOES NOT INSERT ADMIN INTO USER_ROLE JOIN TABLE
  }
}

This works 100% exactly the way I would expect if I remove the custom validator. It may also work if I don't run this in PostConstruct and schedule it in a different thread, I need to check that.

Project with reproducible failing test case: https://github.com/tjhelmuth/SPR-22533/blob/master/src/test/java/spr22533/bug/BugExample.java

回答1:

Accessing the EntityManager during validation is not guaranteed to work during validation.

Validation happens in "lifecycle callback methods". For these the following restriction applies (Java Persistence Specification 2.2; Section 3.5.2 Lifecycle Callback Methods):

In general, the lifecycle method of a portable application should not invoke EntityManager or query operations, access other entity instances, or modify relationships within the same persistence context. A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.

To make it work, use a separate EntityManager, which of course might suffer from seeing a different set of changes since it runs a different transaction.

See also: Correct way to do an EntityManager query during Hibernate Validation