Reusing JPA models using embedded entities and composition

We’ve all heard of the debate regarding inheritance vs composition. Composition is certainly favored more over inheritance unless there is an “is-a” relationship between two classes.

In the ORM world, specifically in JPA, composition is achieved using join-relationships (i.e. One-To-One, One-To-Many, Many-To-One, Many-To-Many), often overlooked is the functionality to embed a class in your models using the Embeddable and Embedded annotations. In this article I hope to present a simple design pattern that demonstrates the usefulness of this feature.

To demonstrate this feature, we will use the following problem scenario:

“You are tasked to create a simple bulletin board service. The requirements are simple, a user will login, after which he or she can create a bulletin post or comment on other public posting. Additionally, other users can edit a bulletin posting or the same user can edit his or her comment, but the system must be able to show who created the posting or made the last change and when it occurred. It is not a requirement to track the change history, just the last update.”

For our initial model design, we need two concrete entity classes, a Bulletin model for all bulletin postings and a Comment model to store comments. We will also include a mapped superclass to store common fields like the model id and optimistic lock. We now have the ability to store bulletin posting and comments associated with it in our database. How do we now keep track of updates to each entity?

A simple solution would be to have a dateCreated, createdBy, dateUpdated, and updateBy field for each entity (we will refer to them as our auditing information). Here is where the inheritance vs composition decision comes in. Since both models require auditing information, do we create a superclass to encapsulate the functionality (inheritance) or do we create a separate field to store the auditing information (composition)?

For this scenario, I will choose composition because:

    Non-invasive - using composition injects the auditing feature to the specific class that needs it, as oppose to inheritance where subclasses will inherit the auditing feature whether they need it or not.
    Flexible - using composition allows multiple features to be added to a class without worrying about the one parent class per class rule. Example, if you need to make a model auditable and ownable by a specific group of users, you can just add an auditing information and an ownership information property.
    Encapsulation - using composition encapsulates the data and behavior of the feature without mixing it with the data and behavior specific to the class.

Provided below is the interface and embeddable class that will implement our auditing feature for each model.

public interface Auditable {
    public AuditInfo getAuditInfo();

    public void setAuditInfo(AuditInfo auditInfo);
}

@Embeddable
public class AuditInfo {

    @Column(length = 50)
    protected String createdBy;

    @Temporal(TemporalType.TIMESTAMP)
    protected Date dateCreated;

    @Column(length = 50)
    protected String updatedBy;

    @Temporal(TemporalType.TIMESTAMP)
    protected Date dateUpdated;

    // Methods/getters/setters removed for simplicity
}

The Embeddable annotation specifies that an entity can be embedded in another entity class.

For our Bulletin and Comment model, we use the following implementations.

@Entity
@EntityListeners(AuditListener.class)
@Table(name = "bulletin")
public class Bulletin extends AbstractModel implements Auditable {

    @Column(length = 255)
    protected String title;

    @Lob @Basic(fetch = FetchType.EAGER)
    protected String body;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "bulletin")
    protected List comments = new ArrayList();

    @Embedded
    protected AuditInfo auditInfo = new AuditInfo();

    // Methods/getters/setters removed for simplicity

}

@Entity
@EntityListeners(AuditListener.class)
@Table(name = "comment")
public class Comment extends AbstractModel implements Auditable {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "BULLETIN_id", referencedColumnName = "id", nullable = false, updatable = false)
    protected Bulletin bulletin;

    @Column(length = 255)
    protected String comment;

    @Embedded
    protected AuditInfo auditInfo = new AuditInfo();

    // Methods/getters/setters removed for simplicity

}

We use the Embedded annotation to specify that the field is an embeddable entity.

You would also notice that we have an Auditable interface that contains a getter and setter. This interface plays an important role in providing type checking to our models when used together with EntityListeners. Provided below is a sample of our entity listener that will implement the auditing functionality.

public class AuditListener {

    private final transient Logger log = LoggerFactory.getLogger(AuditListener.class);

    @PrePersist
    public void setCreatedBy(Auditable model) {
        model.getAuditInfo().setCreatedBy(UserContext.getCurrentUser());
        model.getAuditInfo().setDateCreated(new Date());
        log.debug("Auditing model on prepersist: " + model);
    }

    @PreUpdate
    public void setUpdatedBy(Auditable model) {
        model.getAuditInfo().setUpdatedBy(UserContext.getCurrentUser());
        model.getAuditInfo().setDateUpdated(new Date());
        log.debug("Auditing model on preupdate: " + model);
    }
}

With the Auditable interface, we are provided type checking such that only “Auditable” models can be used in the AuditListener. (Note: this also presents some problems in OpenJPA, since it uses reflection to invoke the entity listeners. An obscure type mismatch exception will occur, if a non-”Auditable” model is passed to the listener.)

That’s it for using composition and embeddable entities in JPA. For a complete sample, you can download the source code here.

COMMENTS / 4 COMMENTS

Tried this example in my code to replace inheritence scheme to do basically the same thing. Wouldn’t run in Jboss/Seam - got an TypeNotPresentExceptionProxy error on the EntityListeners annotation (at least it goes away when I comment that code out, but then the rest still had some problems I was too lazy to figure out).

Jason Christian added these pithy words on Apr 25 08 at 3:41 pm

Hi, pretty interesting article, but I was wondering if you could give me a hint on some problem related to this article… I was wondering if there is a way of defining a preLoad method or a where clause implied, or a method of extending the @ManyToOne annotation and modify it in order to achieve the result which is to have an extra query over all the joins and all the selects executed by JPA. Thanks in advance.

alex added these pithy words on May 07 08 at 8:03 am

@Jason: What persistence provider does Jboss/Seam use? Hibernate? I haven’t actually tested the code in Hibernate. I’ll try it when I get the time. :)

Adrian Co added these pithy words on May 07 08 at 2:53 pm

@Alex: I’m not sure if OpenJPA has this feature. Hibernate does have filters [http://www.hibernate.org/hib_docs/reference/en/html/filters.html]. I don’t know if the SQLEmbed class can do the trick [http://openjpa.apache.org/docs/latest/javadoc/org/apache/openjpa/jdbc/kernel/exps/SQLEmbed.html]. I’m actually interested in a similar feature. :)

Adrian Co added these pithy words on May 07 08 at 3:11 pm

SPEAK / ADD YOUR COMMENT
Comments are moderated.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Spam Protection by WP-SpamFree

Return to Top