Home > Back-end >  Hibernate @OneToMany removing from Set doesn't trigger constraint validation on update of paren
Hibernate @OneToMany removing from Set doesn't trigger constraint validation on update of paren

Time:10-29

I have the following scenario:

@Entity
@Table(name = "groups_supervisors")

public class SupervisorEntity extends AbstractEntity {
  
  @ManyToOne
  @EqualsAndHashCode.Exclude
  @JoinColumn(name = "group_id")
  KibanaAdGroupEntity kibanaAdGroup;
  
   //Another fields

  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;

    SupervisorEntity that = (SupervisorEntity) o;

    if (!Objects.equals(id, that.id))
      return false;
    if (kibanaAdGroup!=null && !kibanaAdGroup.getId().equals(that.kibanaAdGroup.getId()))
      return false;
    if (!Objects.equals(name, that.name))
      return false;
    if (!Objects.equals(updateDate, that.updateDate))
      return false;
    return Objects.equals(dateAdd, that.dateAdd);
  }

  @Override
  public int hashCode() {
    int result = id.hashCode();
    result = 31 * result   (kibanaAdGroup != null ? kibanaAdGroup.getId().hashCode() : 0);
    result = 31 * result   name.hashCode();
    result = 31 * result   updateDate.hashCode();
    result = 31 * result   dateAdd.hashCode();
    return result;
  }

}
@Entity
@Data
@Table(name = "ad_groups")
public class KibanaAdGroupEntity extends AbstractEntity {
     
  @NotNull 
  @Size(min = 2)
  @BatchSize(size = 10)
  @ToString.Exclude
  @OneToMany(mappedBy = "kibanaAdGroup", cascade = {CascadeType.ALL}, orphanRemoval =true)
  Set<SupervisorEntity> supervisors;
  
  //Some other fields
  
  //Destroy releation
  public Set<SupervisorEntity> removeSupervisor(SupervisorEntity supervisor) {
    supervisors.remove(supervisor);
    supervisor.setKibanaAdGroup(null);
    return supervisors;
  }

}

When I'm trying to perform following

  @Transactional
  public void test() {
    var adGroup = adGroupRepository.findAll().stream().limit(1000).filter(adGr -> adGr.getId() == 142)
      .findFirst().get();
    var supervisor = adGroup.getSupervisors().iterator().next();
    adGroup.removeSupervisor(supervisor);
    adGroupRepository.save(adGroup);
  }

expected that adGroupRepository.save(adGroup) will perform update cascade on adGroup and constraint @Size(min = 2) on Set<SupervisorEntity> supervisors will trigger an exception. I tried to debug DefaultFlushEntityEventListener::dirtyCheck , but I see that an old entity and new one one contain identical size of supervisors Please can any one clarify to me where I've mistaken and how to trigger update/checking of this constraint?

UPD In logs and DB I see that supervisor was successfully deleted

2022-10-23 10:37:07.022 DEBUG [4b5430e5fcb074b6,4b5430e5fcb074b6] 93041 - [8080-exec-1] org.hibernate.SQL                : \n     delete \n     from\n         public.groups_supervisors \n     where\n         id=? 
Hibernate: 
    delete 
    from
        public.groups_supervisors 
    where
        id=?

CodePudding user response:

For the lazy loaded association mapping , it is suggested to place the validation annotations on the getter rather on the field because of the following reasons mentioned from the doc :

When lazy loaded associations are supposed to be validated it is recommended to place the constraint on the getter of the association. Hibernate ORM replaces lazy loaded associations with proxy instances which get initialized/loaded when requested via the getter. If, in such a case, the constraint is placed on field level, the actual proxy instance is used which will lead to validation errors.

So please try :

@Entity
@Data
@Table(name = "ad_groups")
public class KibanaAdGroupEntity extends AbstractEntity {

  
  @BatchSize(size = 10)
  @OneToMany(mappedBy = "kibanaAdGroup", cascade = {CascadeType.ALL}, orphanRemoval =true)
  Set<SupervisorEntity> supervisors;

   @NotNull 
   @Size(min = 2)
   Set<SupervisorEntity> getSupervisors(){
      return this.supervisors;
   }

}

CodePudding user response:

Seems that I found the problem. This happens due to what type hibernate will inject into Set field. When you perform findAll - Set field will be injected as PersistenSet type by default. This is shared reference between yours business logic side and dirty check mechanism of hibernate. So when you

adGroup.removeSupervisor(supervisor);
adGroupRepository.save(adGroup);

That operation triggers DefaultFlushEntityEventListener::dirtyCheck. But due to final Object[] loadedState = entry.getLoadedState(); keeps shared reference, for hibernate it looks like entity wasn't change state of the entity. That's why no validation error was occurred(but child entity will be deleted as expected by orphan removal) PS If you will change any field of the entity validation will be performed as expected

  • Related