12.3. Data filtering and interception

Imagine that you don't want to see all the data in your database. For example, the currently logged-in application user may not have the rights to see everything. Usually, you add a condition to your queries and restrict the result dynamically. This becomes difficult if you have to handle a concern such as security or temporal data ("Show me only data from last week," for example). Even more difficult is a restriction on collections; if you iterate through the Item objects in a Category, you'll see all of them.

One possible solution for this problem uses database views. SQL doesn't standardize dynamic views—views that can be restricted and moved at runtime with some parameter (the currently logged-in user, a time period, and so on). Few databases offer more flexible view options, and if they're available, they're pricey and/or complex (Oracle offers a Virtual Private Database addition, for example).

Hibernate provides an alternative to dynamic database views: data filters with dynamic parameterization at runtime. We'll look at the use cases and application of data filters in the following sections.

Another common issue in database applications is crosscutting concerns that require knowledge of the data that is stored or loaded. For example, imagine that you have to write an audit log of every data modification in your application. Hibernate offers an org.hibernate.Interceptor interface that allows you to hook into the internal processing of Hibernate and execute side effects such as audit logging. You can do much more with interception, and we'll show you a few tricks after we've completed our discussion of data filters.

The Hibernate core is based on an event/listener model, a result of the last refactoring of the internals. If an object must be loaded, for example, a LoadEvent is fired. The Hibernate core is implemented as default listeners for such events, and this system has public interfaces that let you plug in your own listeners if you like. The event system offers complete customization of any imaginable operation that happens inside Hibernate, and should be considered a more powerful alternative to interception—we'll show you how to write a custom listener and handle events yourself.

Let's first apply dynamic data filtering in a unit of work.

12.3.1. Dynamic data filters

The first use case for dynamic data filtering is related to data security. A User in CaveatEmptor has a ranking property. Now assume that users can only bid on items that are offered by other users with an equal or lower rank. In business terms, you have several groups of users that are defined by an arbitrary rank (a number), and users can trade only within their group.

You can implement this with complex queries. For example, let's say you want to show all the Item objects in a Category, but only those items that are sold by users in the same group (with an equal or lower rank than the logged-in user). You'd write an HQL or Criteria query to retrieve these items. However, if you use aCategory.getItems() and navigate to these objects, all Item instances would be visible.

You solve this problem with a dynamic filter.

Defining a data filter

A dynamic data filter is defined with a global unique name, in mapping metadata. You can add this global filter definition in any XML mapping file you like, as long as it's inside a <hibernate-mapping> element:

<filter-def name="limitItemsByUserRank">
    <filter-param name="currentUserRank" type="int"/>
</filter-def>

This filter is named limitItemsByUserRank and accepts one runtime argument of type int. You can put the equivalent @org.hibernate.annotations.FilterDef annotation on any class you like (or into package metadata); it has no effect on the behavior of that class:

@org.hibernate.annotations.FilterDef(
    name="limitItemsByUserRank",
    parameters = {
        @org.hibernate.annotations.ParamDef(
            name = "currentUserRank", type = "int"
        )
    }
)

The filter is inactive now; nothing (except maybe the name) indicates that it's supposed to apply to Item objects. You have to apply and implement the filter on the classes or collections you want to filter.

Applying and implementing the filter

You want to apply the defined filter on the Item class so that no items are visible if the logged-in user doesn't have the necessary rank:

<class name="Item" table="ITEM">
    ...

    <filter name="limitItemsByUserRank"
            condition=":currentUserRank >=
                       (select u.RANK from USER u
                        where u.USER_ID = SELLER_ID)"/>
</class>

The <filter> element can be set for a class mapping. It applies a named filter to instances of that class. The condition is an SQL expression that's passed through directly to the database system, so you can use any SQL operator or function. It must evaluate to true if a record should pass the filter. In this example, you use a subquery to obtain the rank of the seller of the item. Unqualified columns, such as SELLER_ID, refer to the table to which the entity class is mapped. If the currently logged-in user's rank isn't greater than or equal than the rank returned by the subquery, the Item instance is filtered out.

Here is the same in annotations on the Item entity:

@Entity
@Table(name = "ITEM")
@org.hibernate.annotations.Filter(
    name = "limitItemsByUserRank",
    condition=":currentUserRank >= " +
              "(select u.RANK from USER u" +
              " where u.USER_ID = SELLER_ID)"
)
public class Item implements { ... }

You can apply several filters by grouping them within a @org.hibernate.annotations.Filters annotation. A defined and applied filter, if enabled for a particular unit of work, filters out any Item instance that doesn't pass the condition. Let's enable it.

Enabling the filter

You've defined a data filter and applied it to a persistent class. It's still not filtering anything; it must be enabled and parameterized in the application for a particular Session (the EntityManager doesn't support this API—you have to fall back to Hibernate interfaces for this functionality):

Filter filter = session.enableFilter("limitItemsByUserRank");
filter.setParameter("currentUserRank", loggedInUser.getRanking());

You enable the filter by name; this method returns a Filter instance. This object accepts the runtime arguments. You must set the parameters you have defined. Other useful methods of the Filter are getFilterDefinition() (which allows you to iterate through the parameter names and types) and validate() (which throws a HibernateException if you forgot to set a parameter). You can also set a list of arguments with setParameterList(), this is mostly useful if your SQL condition contains an expression with a quantifier operator (the IN operator, for example).

Now every HQL or Criteria query that is executed on the filtered Session restricts the returned Item instances:

List<Item> filteredItems =
                session.createQuery("from Item").list();
List<Item> filteredItems =
                session.createCriteria(Item.class).list();

Two object-retrieval methods are not filtered: retrieval by identifier and navigational access to Item instances (such as from a Category with aCategory.getItems()).

Retrieval by identifier can't be restricted with a dynamic data filter. It's also conceptually wrong: If you know the identifier of an Item, why shouldn't you be allowed to see it? The solution is to filter the identifiers—that is, not expose identifiers that are restricted in the first place. Similar reasoning applies to filtering of many-to-one or one-to-one associations. If a many-to-one association was filtered (for example, by returning null if you call anItem.getSeller()), the multiplicity of the association would change! This is also conceptually wrong and not the intent of filters.

You can solve the second issue, navigational access, by applying the same filter on a collection.

Filtering collections

So far, calling aCategory.getItems() returns all Item instances that are referenced by that Category. This can be restricted with a filter applied to a collection:

<class name="Category" table="CATEGORY">

    ...

    <set name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID">

           <filter name="limitItemsByUserRank"
                    condition=":currentUserRank >=
                               (select u.RANK from USERS u where
u.USER_ID = SELLER_ID)"/> </many-to-many> </set> </class>

In this example, you don't apply the filter to the collection element but to the <many-to-many>. Now the unqualified SELLER_ID column in the subquery references the target of the association, the ITEM table, not the CATEGORY_ITEM join table of the association. With annotations, you can apply a filter on a many-to-many association with @org.hibernate.annotations.FilterJoinTable(s) on the @ManyToMany field or getter method.

If the association between Category and Item was one-to-many, you'd created the following mapping:

<class name="Category" table="CATEGORY">

    ...

    <set name="items">
        <key column="CATEGORY_ID"/>
        <one-to-many class="Item"/>

        <filter name="limitItemsByUserRank"
                condition=":currentUserRank >=
                           (select u.RANK from USERS u
where u.USER_ID = SELLER_ID)"/> </set> </class>

With annotations, you just place the @org.hibernate.annotations.Filter(s) on the right field or getter method, next to the @OneToMany or @ManyToMany annotation.

If you now enable the filter in a Session, all iteration through a collection of items of a Category is filtered.

If you have a default filter condition that applies to many entities, declare it with your filter definition:

<filter-def name="limitByRegion"
            condition="REGION >= :showRegion">
    <filter-param name="showRegion" type="int"/>
</filter-def>

If applied to an entity or collection with or without an additional condition and enabled in a Session, this filter always compares the REGION column of the entity table with the runtime showRegion argument.

There are many other excellent use cases for dynamic data filters.

Use cases for dynamic data filters

Hibernate's dynamic filters are useful in many situations. The only limitation is your imagination and your skill with SQL expressions. Typical use cases are as follows:

  • Security limits—A common problem is the restriction of data access given some arbitrary security-related condition. This can be the rank of a user, a particular group the user must belong to, or a role the user has been assigned.

  • Regional data—Often, data is stored with a regional code (for example, all business contacts of a sales team). Each salesperson works only on a dataset that covers their region.

  • Temporal data—Many enterprise applications need to apply time-based views on data (for example, to see a dataset as it was last week). Hibernate's data filters can provide basic temporal restrictions that help you implement this kind of functionality.

Another useful concept is the interception of Hibernate internals, to implement orthogonal concerns.

12.3.2. Intercepting Hibernate events

Let's assume that you want to write an audit log of all object modifications. This audit log is kept in a database table that contains information about changes made to other data—specifically, about the event that results in the change. For example, you may record information about creation and update events for auction Items. The information that is recorded usually includes the user, the date and time of the event, what type of event occurred, and the item that was changed.

Audit logs are often handled using database triggers. On the other hand, it's sometimes better for the application to take responsibility, especially if portability between different databases is required.

You need several elements to implement audit logging. First, you have to mark the persistent classes for which you want to enable audit logging. Next, you define what information should be logged, such as the user, date, time, and type of modification. Finally, you tie it all together with an org.hibernate.Interceptor that automatically creates the audit trail.

Creating the marker interface

First, create a marker interface, Auditable. You use this interface to mark all persistent classes that should be automatically audited:

package auction.model;

public interface Auditable {
    public Long getId();
}

This interface requires that a persistent entity class exposes its identifier with a getter method; you need this property to log the audit trail. Enabling audit logging for a particular persistent class is then trivial. You add it to the class declaration—for example, for Item:

public class Item implements Auditable { ... }

Of course, if the Item class didn't expose a public getId() method, you'd need to add it.

Creating and mapping the log record

Now create a new persistent class, AuditLogRecord. This class represents the information you want to log in your audit database table:

public class AuditLogRecord {

    public String message;
    public Long entityId;
    public Class entityClass;
    public Long userId;
    public Date created;

    AuditLogRecord() {}

    public AuditLogRecord(String message,
                          Long entityId,
                          Class entityClass,
                          Long userId) {
        this.message = message;
        this.entityId = entityId;
        this.entityClass = entityClass;
        this.userId = userId;
        this.created = new Date();
    }
}

You shouldn't consider this class part of your domain model! Hence you expose all attributes as public; it's unlikely you'll have to refactor that part of the application. The AuditLogRecord is part of your persistence layer and possibly shares the same package with other persistence related classes, such as HibernateUtil or your custom UserType extensions.

Next, map this class to the AUDIT_LOG database table:

<hibernate-mapping default-access="field">

<class name="persistence.audit.AuditLogRecord"
       table="AUDIT_LOG" mutable="false">

    <id type="long" column="AUDIT_LOG_ID">
        <generator class="native"/>
    </id>

    <property   name="message"
                type="string"
                column="MESSAGE"
                length="255"

                not-null="true"/>

    <property   name="entityId"
                type="long"
                column="ENTITY_ID"
                not-null="true"/>

    <property   name="entityClass"
                type="class"
                column="ENTITY_CLASS"
                not-null="true"/>

    <property   name="userId"
                type="long"
                column="USER_ID"
                not-null="true"/>

    <property   name="created"
                column="CREATED"
                type="java.util.Date"
                update="false"
                not-null="true"/>

</class>

</hibernate-mapping>

You map the default access to a field strategy (no getter methods in the class) and, because AuditLogRecord objects are never updated, map the class as mutable="false". Note that you don't declare an identifier property name (the class has no such property); Hibernate therefore manages the surrogate key of an AuditLogRecord internally. You aren't planning to use the AuditLogRecord in a detached fashion, so it doesn't need to contain an identifier property. However, if you mapped this class with annotation as a Java Persistence entity, an identifier property would be required. We think that you won't have any problems creating this entity mapping on your own.

Audit logging is a somewhat orthogonal concern to the business logic that causes the loggable event. It's possible to mix logic for audit logging with the business logic, but in many applications it's preferable that audit logging be handled in a central piece of code, transparently to the business logic (and especially when you rely on cascading options). Creating a new AuditLogRecord and saving it whenever an Item is modified is certainly something you wouldn't do manually. Hibernate offers an Interceptor extension interface.

Writing an interceptor

A logEvent() method should be called automatically when you call save(). The best way to do this with Hibernate is to implement the Interceptor interface. Listing 12.1 shows an interceptor for audit logging.

Listing 12-1. Implementation of an interceptor for audit logging
public class AuditLogInterceptor extends EmptyInterceptor {

    private Session session;
    private Long userId;

    private Set inserts = new HashSet();
    private Set updates = new HashSet();

    public void setSession(Session session) {
        this.session=session;
    }

    public void setUserId(Long userId) {
        this.userId=userId;
    }

    public boolean onSave(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types)
            throws CallbackException {

        if (entity instanceof Auditable)
            inserts.add(entity);

        return false;
    }

    public boolean onFlushDirty(Object entity,
                                Serializable id,
                                Object[] currentState,
                                Object[] previousState,
                                String[] propertyNames,
                                Type[] types)
            throws CallbackException {
        if (entity instanceof Auditable)
            updates.add(entity);

        return false;
    }

    public void postFlush(Iterator iterator)
                    throws CallbackException {
        try {
            for (Iterator it = inserts.iterator(); it.hasNext();) {

                Auditable entity = (Auditable) it.next();
                AuditLog.logEvent("create",
                                  entity,
                                  userId,
                                  session.connection());
            }
            for (Iterator it = updates.iterator(); it.hasNext();) {
                Auditable entity = (Auditable) it.next();
                AuditLog.logEvent("update",
                                  entity,
                                  userId,
                                  session.connection());
            }
        } finally {
            inserts.clear();
            updates.clear();
        }
    }
}

The Hibernate Interceptor API has many more methods than shown in this example. Because you're extending the EmptyInterceptor, instead of implementing the interface directly, you can rely on default semantics of all methods you don't override. The interceptor has two interesting aspects.

This interceptor needs the session and userId attributes to do its work; a client using this interceptor must set both properties. The other interesting aspect is the audit-log routine in onSave() and onFlushDirty(): You add new and updated entities to the inserts and updates collections. The onSave() interceptor method is called whenever an entity is saved by Hibernate; the onFlushDirty() method is called whenever Hibernate detects a dirty object.

The actual logging of the audit trail is done in the postFlush() method, which Hibernate calls after executing the SQL that synchronizes the persistence context with the database. You use the static call AuditLog.logEvent() (a class and method we discuss next) to log the event. Note that you can't log events in onSave(), because the identifier value of a transient entity may not be known at this point. Hibernate guarantees to set entity identifiers during flush, so postFlush() is the correct place to log this information.

Also note how you use the session: You pass the JDBC connection of a given Session to the static call to AuditLog.logEvent(). There is a good reason for this, as we'll discuss in more detail.

Let's first tie it all together and see how you enable the new interceptor.

Enabling the interceptor

You need to assign the Interceptor to a Hibernate Session when you first open the session:

AuditLogInterceptor interceptor = new AuditLogInterceptor();
Session session = getSessionFactory().openSession(interceptor);
Transaction tx = session.beginTransaction();

interceptor.setSession(session);
interceptor.setUserId( currentUser.getId() );

session.save(newItem); // Triggers onSave() of the Interceptor

tx.commit();
session.close();

The interceptor is active for the Session you open it with.

If you work with sessionFactory.getCurrentSession(), you don't control the opening of a Session; it's handled transparently by one of Hibernate's built-in implementations of CurrentSessionContext. You can write your own (or extend an existing) CurrentSessionContext implementation and supply your own routine for opening the current Session and assigning an interceptor to it.

Another way to enable an interceptor is to set it globally on the Configuration with setInterceptor() before building the SessionFactory. However, any interceptor that is set on a Configuration and active for all Sessions must be implemented thread-safe! The single Interceptor instance is shared by concurrently running Sessions. The AuditLogInterceptor implementation isn't thread-safe: It uses member variables (the inserts and updates queues).

You can also set a shared thread-safe interceptor that has a no-argument constructor for all EntityManager instances in JPA with the following configuration option in persistence.xml:

<persistence-unit name="...">
  <properties>
      <property name="hibernate.ejb.interceptor"
                value="my.ThreadSafeInterceptorImpl"/>
      ...
  </properties>
</persistence-unit>

Let's get back to that interesting Session-handling code in the interceptor and find out why you pass the connection() of the current Session to AuditLog.logEvent().

Using a temporary Session

It should be clear why you require a Session inside the AuditLogInterceptor. The interceptor has to create and persist AuditLogRecord objects, so a first attempt for the onSave() method could be the following routine:

if (entity instanceof Auditable) {

    AuditLogRecord logRecord = new AuditLogRecord(...);
    // set the log information

    session.save(logRecord);
}

This seems straightforward: Create a new AuditLogRecord instance and save it, using the currently running Session. This doesn't work.

It's illegal to invoke the original Hibernate Session from an Interceptor callback. The Session is in a fragile state during interceptor calls. You can't save() a new object during the saving of other objects! A nice trick that avoids this issue is opening a new Session only for the purpose of saving a single AuditLogRecord object. You reuse the JDBC connection from the original Session.

This temporary Session handling is encapsulated in the AuditLog class, shown in listing 12.2.

Listing 12-2. The AuditLog helper class uses a temporary Session
public class AuditLog {

    public static void logEvent(
        String message,
        Auditable entity,
        Long userId,
        Connection connection) {

        Session tempSession =
                    getSessionFactory().openSession(connection);

        try {
            AuditLogRecord record =
                new AuditLogRecord(message,
                                   entity.getId(),
                                   entity.getClass(),
                                   userId );

            tempSession.save(record);
            tempSession.flush();
        } finally {
            tempSession.close();

        }
    }
}

The logEvent() method uses a new Session on the same JDBC connection, but it never starts or commits any database transaction. All it does is execute a single SQL statement during flushing.

This trick with a temporary Session for some operations on the same JDBC connection and transaction is sometimes useful in other situations. All you have to remember is that a Session is nothing more than a cache of persistent objects (the persistence context) and a queue of SQL operations that synchronize this cache with the database.

We encourage you to experiment and try different interceptor design patterns. For example, you could redesign the auditing mechanism to log any entity, not only Auditable. The Hibernate website also has examples using nested interceptors or even for logging a complete history (including updated property and collection information) for an entity.

The org.hibernate.Interceptor interface also has many more methods that you can use to hook into Hibernate's processing. Most of them let you influence the outcome of the intercepted operation; for example, you can veto the saving of an object. We think that interception is almost always sufficient to implement any orthogonal concern.

Having said that, Hibernate allows you to hook deeper into its core with the extendable event system it's based on.

12.3.3. The core event system

Hibernate 3.x was a major redesign of the implementation of the core persistence engine compared to Hibernate 2.x. The new core engine is based on a model of events and listeners. For example, if Hibernate needs to save an object, an event is triggered. Whoever listens to this kind of event can catch it and handle the saving of the object. All Hibernate core functionalities are therefore implemented as a set of default listeners, which can handle all Hibernate events.

This has been designed as an open system: You can write and enable your own listeners for Hibernate events. You can either replace the existing default listeners or extend them and execute a side effect or additional procedure. Replacing the event listeners is rare; doing so implies that your own listener implementation can take care of a piece of Hibernate core functionality.

Essentially, all the methods of the Session interface correlate to an event. The load() method triggers a LoadEvent, and by default this event is processed with the DefaultLoadEventListener.

A custom listener should implement the appropriate interface for the event it wants to process and/or extend one of the convenience base classes provided by Hibernate, or any of the default event listeners. Here's an example of a custom load event listener:

public class SecurityLoadListener extends DefaultLoadEventListener {

    public void onLoad(LoadEvent event,
                       LoadEventListener.LoadType loadType)
            throws HibernateException {

        if ( !MySecurity.isAuthorized(
                event.getEntityClassName(), event.getEntityId()
              )
           ) {
            throw MySecurityException("Unauthorized access");
        }

        super.onLoad(event, loadType);
    }
}

This listener calls the static method isAuthorized() with the entity name of the instance that has to be loaded and the database identifier of that instance. A custom runtime exception is thrown if access to that instance is denied. If no exception is thrown, the processing is passed on to the default implementation in the superclass.

Listeners should be considered effectively singletons, meaning they're shared between requests and thus shouldn't save any transaction related state as instance variables. For a list of all events and listener interfaces in native Hibernate, see the API Javadoc of the org.hibernate.event package. A listener implementation can also implement multiple event-listener interfaces.

Custom listeners can either be registered programmatically through a Hibernate Configuration object or specified in the Hibernate configuration XML (declarative configuration through the properties file isn't supported). You also need a configuration entry telling Hibernate to use the listener in addition to the default listener:

<session-factory>

  ...

  <event type="load">

    <listener class="auction.persistence.MyLoadListener"/>
  </event>

</session-factory>

Listeners are registered in the same order they're listed in your configuration file. You can create a stack of listeners. In this example, because you're extending the built-in DefaultLoadEventListener, there is only one. If you didn't extend the DefaultLoadEventListener, you'd have to name the built-in DefaultLoadEventListener as the first listener in your stack—otherwise you'd disable loading in Hibernate!

Alternatively you may register your listener stack programmatically:

Configuration cfg = new Configuration();

LoadEventListener[] listenerStack =
    { new MyLoadListener(), ... };

cfg.getEventListeners().setLoadEventListeners(listenerStack);

Listeners registered declaratively can't share instances. If the same class name is used in multiple <listener/> elements, each reference results in a separate instance of that class. If you need the capability to share listener instances between listener types, you must use the programmatic registration approach.

Hibernate EntityManager also supports customization of listeners. You can configure shared event listeners in your persistence.xml configuration as follows:

<persistence-unit name="...">
  <properties>
      <property name="hibernate.ejb.event.load"
                value="auction.persistence.MyLoadListener, ..."/>
      ...
  </properties>
</persistence-unit>

The property name of the configuration option changes for each event type you want to listen to (load in the previous example).

If you replace the built-in listeners, as MyLoadListener does, you need to extend the correct default listeners. At the time of writing, Hibernate EntityManager doesn't bundle its own LoadEventListener, so the listener that extends org.hibernate.event.DefaultLoadEventListener still works fine. You can find a complete and up-to-date list of Hibernate EntityManager default listeners in the reference documentation and the Javadoc of the org.hibernate.ejb.event package. Extend any of these listeners if you want to keep the basic behavior of the Hibernate EntityManager engine.

You rarely have to extend the Hibernate core event system with your own functionality. Most of the time, an org.hibernate.Interceptor is flexible enough. It helps to have more options and to be able to replace any piece of the Hibernate core engine in a modular fashion.

The EJB 3.0 standard includes several interception options, for session beans and entities. You can wrap any custom interceptor around a session bean method call, intercept any modification to an entity instance, or let the Java Persistence service call methods on your bean on particular lifecycle events.

12.3.4. Entity listeners and callbacks

EJB 3.0 entity listeners are classes that intercept entity callback events, such as the loading and storing of an entity instance. This is similar to native Hibernate interceptors. You can write custom listeners, and attach them to entities through annotations or a binding in your XML deployment descriptor.

Look at the following trivial entity listener:

import javax.persistence.*;

public class MailNotifyListener {

    @PostPersist
    @PostLoad

    public void notifyAdmin(Object entity) {
        mail.send("Somebody saved or loaded: " + entity);
    }
}

An entity listener doesn't implement any particular interface; it needs a no-argument constructor (in the previous example, this is the default constructor). You apply callback annotations to any methods that need to be notified of a particular event; you can combine several callbacks on a single method. You aren't allowed to duplicate the same callback on several methods.

The listener class is bound to a particular entity class through an annotation:

import javax.persistence.*;
@Entity
@EntityListeners(MailNotifyListener.class)
public class Item {
    ...

    @PreRemove
    private void cleanup() {
        ...
    }
}

The @EntityListeners annotation takes an array of classes, if you need to bind several listeners. You can also place callback annotations on the entity class itself, but again, you can't duplicate callbacks on methods in a single class. However, you can implement the same callback in several listener classes or in the listener and entity class.

You can also apply listeners to superclasses for the whole hierarchy and define default listeners in your persistence.xml configuration file. Finally, you can exclude superclass listeners or default listeners for a particular entity with the @ExcludeSuperclassListeners and @ExcludeDefaultListeners annotations.

All callback methods can have any visibility, must return void, and aren't allowed to throw any checked exceptions. If an unchecked exception is thrown, and a JTA transaction is in progress, this transaction is rolled back.

A list of available JPA callbacks is shown in Table 12.2.

Table 12-2. JPA event callbacks and annotations
Callback annotationDescription
@PostLoadTriggered after an entity instance has been loaded with find() or getReference(), or when a Java Persistence query is executed. Also called after the refresh() method is invoked.
@PrePersist, @PostPersistOccurs immediately when persist() is called on an entity, and after the database insert.
@PreUpdate, @PostUpdateExecuted before and after the persistence context is synchronized with the database—that is, before and after flushing. Triggered only when the state of the entity requires synchronization (for example, because it's considered dirty).
@PreRemove, @PostRemoveTriggered when remove() is called or the entity instance is removed by cascading, and after the database delete.

Unlike Hibernate interceptors, entity listeners are stateless classes. You therefore can't rewrite the previous Hibernate audit-logging example with entity listeners, because you'd need to hold the state of modified objects in local queues. Another problem is that an entity listener class isn't allowed to use the EntityManager. Certain JPA implementations, such as Hibernate, let you again apply the trick with a temporary second persistence context, but you should look at EJB 3.0 interceptors for session beans and probably code this audit-logging at a higher layer in your application stack.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.98.18