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