Tuesday 8 February 2011

JPA Entity Lifecycle Callback and Listener Annotations

Within the javax.persistence package there are annotations that may be applied to methods of an entity or a mapped superclass to specify that the annotated method must be called for a certain lifecycle event. A lifecycle event being either: Persist, Update, Remove or Load. and each event has a Pre or Post event.

Listeners can also have methods annotated with a lifecycle event. Listeners can be used to hold the business logic that would otherwise be in the entities (or another layer.) This enables the entities to be just POJOs. It also means that the testing of the business logic can be done in isolation.

Below is an example of using lifecycle events to populate an id var and also populate a lastUpdated var, in a mapped superclass:

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final int ID_LENGTH=36;

    @Id
    @Column(length = ID_LENGTH)
    private String id;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    private Date dateCreated;
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModified;

    // constructor(s)

    @PreUpdate
    public void updateLastModified () {
        lastModified = new Date();
    }

    @PrePersist
    protected void generateUUID() {
        id = java.util.UUID.randomUUID().toString();       
        dateCreated = new Date();
        lastModified = new Date();
    }

    .....

}


An simple example of using a Listener could be where an entity has a transient var that needs to be populated after the entity has been persisted, updated or loaded.

public class AvailableCreditListener {

    @PostLoad
    @PostPersist
    @PostUpdate
    public void calculateAvailableCredit{Account account) {

        account.setAvailableCredit(

            account.getBalance().add(
                account.getOverdraftLimit()));
    }

}



The entity class would be annotated with @EntityListeners:

@EntityListeners({AvailableCreditListener.class})
public class AccountEntity extends BaseEntity {

    private BigDecimal balance;
    private BigDecimal overdraftLimit;
    @Transient
    private BigDecimal availableCredit;

    // getters and setters

}


Finally, instead of annotations, an XMl mapping file can be used and deployed with the application to specify default listeners. (This mapping file is referenced by the persistence.xml file.) But an entity can use the @ExcludeDefaultListeners annotation if it does not want to use the default listeners.

@ExcludeDefaultListeners
@Entity
public class AccountEntity extends BaseEntity {

    ....

}