For anyone else who has run into this issue, I’ve updated my Github project with a solution:
I was able to piece together a solution for Hibernate to execute some code in the event an entity is deleted. This was thanks to the Medium article mentioned above, by Josh Harkema, and a post on Vlad Mihalcea’s website. Start by creating a PreDeleteEventListener
whose onPreDelete()
method will handle the PreDeleteEvent
Hibernate issues before deleting an entity.
public class ApplicationPreDeleteEventListener implements PreDeleteEventListener {
private final PropertyEventHandler propertyEventHandler = new PropertyEventHandler();
@Override
public boolean onPreDelete(PreDeleteEvent event) {
Object entity = event.getEntity();
boolean veto = entity == null;
if (! veto && entity instanceof Property<?> property) {
veto = propertyEventHandler.preDelete(property, event.getSession());
}
return veto;
}
}
The onPreDelete()
method returns a boolean that tells Hibernate whether to veto the original delete that triggered the event.
In this implementation, the PropertyEventHandler
uses the provided EventSource
to remove relationships from the given Property
prior to its deletion:
public class PropertyEventHandler {
public boolean preDelete(Property<?> property, EventSource eventSource) {
return removeFromPropertyHolders(property, eventSource) ||
removeFromPropertyRepositories(property, eventSource);
}
protected boolean removeFromPropertyHolders(Property<?> property, EventSource eventSource) {
eventSource.getSession()
.createNativeMutationQuery("""
UPDATE property_holder
SET property_type = NULL
, property_id = NULL
WHERE property_id = :property_id
""")
.setParameter("property_id", property.getId())
.setHibernateFlushMode(FlushMode.MANUAL)
.executeUpdate();
return false;
}
protected boolean removeFromPropertyRepositories(Property<?> property, EventSource eventSource) {
eventSource.getSession()
.createNativeMutationQuery("""
DELETE FROM repository_properties
WHERE property_id = :property_id
AND property_type = :property_type
""")
.setParameter("property_id", property.getId())
.setParameter("property_type", property.getDiscriminator())
.setHibernateFlushMode(FlushMode.MANUAL)
.executeUpdate();
return false;
}
}
In order to enable this mechanism in the project, the ApplicationPreDeleteEventListener
needs to be “integrated” into Hibernate.
There may be other ways of “integrating” event listeners into Hibernate, but this one worked for me.
Create an implementation of Integrator
:
public class ApplicationIntegrator implements Integrator {
@Override
public void integrate(@UnknownKeyFor @NonNull @Initialized Metadata metadata,
@UnknownKeyFor @NonNull @Initialized BootstrapContext bootstrapContext,
@UnknownKeyFor @NonNull @Initialized SessionFactoryImplementor sessionFactory) {
final EventListenerRegistry eventListenerRegistry =
sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, ApplicationPreDeleteEventListener.class);
}
@Override
public void disintegrate(@UnknownKeyFor @NonNull @Initialized SessionFactoryImplementor sessionFactoryImplementor,
@UnknownKeyFor @NonNull @Initialized SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
// We HAVE to override this...
}
}
The
@UnknownKeyFor
,@NonNull
and@Initialized
annotations on theintegrate()
method are from theorg.checkerframework:checker-qual
package, which is necessary to create anIntegrator
.
Next, create an implementation of IntegratorProvider
:
public class ApplicationIntegratorProvider implements IntegratorProvider {
@Override
public List<Integrator> getIntegrators() {
return List.of(new ApplicationIntegrator());
}
}
Finally, tell Hibernate to use the ApplicationIntegratorProvider
. If using Hibernate alone, append this to the project’s persistence.xml
:
<persistence ...>
<persistence-unit ...>
...
<properties>
...
<property name="hibernate.integrator_provider" value="com.example.hibernatepolymorph.config.ApplicationIntegratorProvider"/>
</properties>
</persistence-unit>
...
</persistence>
If using Spring Boot, append this to the application.properties
:
spring.jpa.properties.hiberrnate.integrator_provider=package.name.to.ApplicationIntegratorProvider
Now, all of the project’s tests perform as expected.