Events, Interceptors, and Filters
|
This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Three older reference books were consulted as bibliography only while preparing these pages and are not the
primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the
This section’s bibliography lists the reference material consulted while preparing these pages. |
Hibernate exposes several extension points for running code around persistence operations, from the standardized JPA callbacks up to Hibernate’s own lower-level event system.
JPA lifecycle callbacks and entity listeners
@Entity
@EntityListeners(AuditListener.class)
public class Book {
@PrePersist
void onPrePersist() {
this.createdAt = Instant.now();
}
@PreUpdate
void onPreUpdate() {
this.updatedAt = Instant.now();
}
}
public class AuditListener {
@PostLoad
void onLoad(Object entity) {
// external listener -- entity does not need to know about it
}
}
The full set: @PrePersist/@PostPersist, @PreUpdate/@PostUpdate, @PreRemove/@PostRemove, and
@PostLoad. A callback method can live directly on the entity (self-callback, as onPrePersist above) or on a
separate class named via @EntityListeners (external listener, as AuditListener above) — external listeners
keep cross-cutting concerns (auditing, validation) out of the entity class itself. Compare with Spring Data’s
own @CreatedDate/@LastModifiedDate auditing (Spring Data JPA),
which is itself implemented as an @EntityListeners(AuditingEntityListener.class) on top of exactly this
mechanism — reach for the Spring Data annotations directly rather than hand-rolling the same thing again in a
Spring Boot application.
The Hibernate Interceptor
A single, session-wide (or SessionFactory-wide) hook that sees every entity operation, not just callbacks
declared per-entity — useful for a cross-cutting concern that needs to touch every entity type uniformly
without annotating each one:
public class AuditInterceptor implements Interceptor {
@Override
public boolean onFlushDirty(Object entity, Object id, Object[] currentState,
Object[] previousState, String[] propertyNames, Type[] types) {
// inspect/modify currentState before the UPDATE is generated
return true; // state was modified -- tell Hibernate to use it
}
}
// registered at bootstrap:
Session session = sessionFactory.withOptions().interceptor(new AuditInterceptor()).openSession();
Interceptor methods (onSave, onFlushDirty, onDelete, …) are called for every entity that passes
through the session, in contrast to @EntityListeners, which is opted into per entity class.
The native event system: EventType, listeners, and Integrator
Beneath both of the above, every Hibernate operation is actually implemented as firing a typed event
(EventType.PERSIST, EventType.LOAD, EventType.FLUSH_ENTITY, …) to a chain of registered listeners — PrePersistEventListener, PreInsertEventListener, and so on for each event type. This is the lowest-level,
most powerful extension point (it is literally how Hibernate implements its own default persistence behavior),
and correspondingly the least commonly needed directly — reach for it only when neither JPA callbacks nor
Interceptor can express what is needed (e.g. replacing, rather than just observing, part of the default
insert/update algorithm for one specific event type).
An Integrator (org.hibernate.integrator.spi.Integrator, discovered via Java’s ServiceLoader) is how a
library registers its own event listeners, type contributors, or other SPI hooks into Hibernate at bootstrap
without the application needing to wire it manually — the mechanism third-party Hibernate extensions (Envers
included, see Envers Auditing) use to attach themselves.
Dynamic data filters: @FilterDef/@Filter
A filter is a named, parameterized WHERE clause fragment that can be turned on or off per session, applied
transparently to every query (and lazy-load) against the filtered entity/collection while active:
@Entity
@FilterDef(name = "activeOnly", parameters = @ParamDef(name = "isActive", type = Boolean.class))
@Filter(name = "activeOnly", condition = "active = :isActive")
public class Book {
private boolean active;
}
// ...
session.enableFilter("activeOnly").setParameter("isActive", true);
List<Book> activeBooks = session.createQuery("FROM Book", Book.class).getResultList(); // filter applied automatically
Filters are per-session and off by default — forgetting to enable one is a silent no-op (the query just runs
unfiltered), unlike a missing WHERE clause typo in HQL, which at least fails loudly if the syntax is wrong.
Soft delete with @SoftDelete
@SoftDelete (Hibernate 6.4+) marks an entity so a remove()/bulk-delete DELETE becomes, transparently, an
UPDATE setting a deleted-marker column instead of removing the row, and every subsequent query against that
entity automatically excludes soft-deleted rows — without hand-writing a filter or an active/deleted column
check into every query:
@Entity
@SoftDelete(columnName = "deleted", strategy = SoftDeleteType.DELETED)
public class Book {
// no explicit "deleted" field needed -- Hibernate manages the column directly
}
This differs from the @FilterDef/@Filter approach above in that soft-delete exclusion is always on (not an
opt-in per-session toggle) and remove() itself changes meaning, rather than queries against an
already-present boolean column being filtered.