JPA default entity listener

The problem

Sometimes we want to register a default Entity Listeners which are called as soon an entity is persisted, without adding the @EntityListeners annotation to our classes, as in most cases we anyway provide interfaces so we can access data on the entity itself. The problem is, that we cannot annotate our entity listener with any annotation, only the entity itself.

Solution

In JPA we can provide entity mappings in the orm.xml which allows us to define default listeners which are executed on any entity.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
                 version="2.1">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.sterl.example.EntityListener">
                    <!-- optional, annotations will work too -->
                    <pre-persist method-name="fooMethod"/> 
                </entity-listener>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>

Furthermore, the body of the entity-listener may contain elements pre-persist to define method.

Example On GitHub

Example

Let’s assume we want to add to each entity an update time and a create time. We could, of course, implement a base entity, but as we should favor composition over inheritance — which would allow us to add even more common fields to the entity — we want to go with an embedded class. Let’s say TimestampBE.

Embeddable time stamps

This class adds to any entity the create date and update date. We just store UTC longs measured from 1970-01-01T00:00Z.

@Embeddable @Getter @Setter
public class TimestampBE implements Serializable {
    @Column(name = "create_date", nullable = false, updatable = false)
    private Long createDate;
    @Column(name = "update_date", nullable = false, updatable = true)
    private Long updateDate;
}

Tag interface

Next, we add an interface that allows us to tag any entity that has a timestamp.

/**
 * Interface which marks an entity that it has the timestamp entity.
 */
public interface HasTimestamp {
    TimestampBE getTimestamp();
    void setTimestamp(TimestampBE timestamp);
}

Timestamp Entity Listener

Now we have to define the behavior which sets us the create and update date in the live cycle of the entity.

public class TimestampEntityListener {
    @PrePersist
    void prePersist(Object entity) {
        if (entity instanceof HasTimestamp) {
            TimestampBE timestamp = getTimestamp((HasTimestamp)entity);
            final long now = Clock.systemUTC().millis();
            timestamp.setCreateDate(now);
            timestamp.setUpdateDate(now);
        }
    }

    @PreUpdate
    void preUpdate(Object entity) {
        if (entity instanceof HasTimestamp) {
            TimestampBE timestamp = getTimestamp((HasTimestamp)entity);
            final long now = Clock.systemUTC().millis();
            if (timestamp.getCreateDate() == null) timestamp.setCreateDate(now);
            timestamp.setUpdateDate(now);
        }
    }
    
    private TimestampBE getTimestamp(HasTimestamp ht) {
        TimestampBE timestamp = ht.getTimestamp();
        if (timestamp == null) {
            timestamp = new TimestampBE();
            ht.setTimestamp(timestamp);
        }
        return timestamp;
    }
}

Reister the default listener

Add the src/main/resources/META-INF/orm.xml and register the Listener above. Adjust the package as needed:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
                 version="2.1">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.sterl.jpa.timestamp.TimestampEntityListener" />
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>

Use the new classes

In the end, you may add the new class at any entity:

@Entity
@Data
@NoArgsConstructor @AllArgsConstructor @Builder
public class PersonBE implements HasTimestamp, Serializable {
    @Id 
    @GeneratedValue
    @Column(name = "person_id")
    private Long id;
    private String name;
    @Embedded
    private TimestampBE timestamp;
}

Paul Sterl has written 54 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>