Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Wednesday, August 24, 2011

Inheritance in the Play! Framework

In the Play Framework, model classes inherit from GenericModel if they provide their own @Id, or Model (if they don't).  When your model classes share fields that are expected to be saved, use a mapped superclass (@MappedSuper class).

To support auditing and versioning, a database design may repeat a set of timestamped and numeric columns on each table.  For example, this E-R diagram shows three tables that have the same CREATE_DT, LAST_UPDATE_DT, and VERSION attribute defined.

Tables with Repeated Auditing and Versioning Columns
Mapped Superclasses

As this is the Play Framework, these tables are realized with model classes that extend GenericModel.  The classes that will support BD_TUTORIAL, BD_TEXTUAL_PROC, and BD_VIDEO don't extend Model (which is used in many of the examples) because these tables need to define their own @Ids.  Both GenericModel and Model are annotated with @MappedSuperclass which means that any fields they define will be persisted along with any accrued in subclasses.

If the @MappedSuperclass annotation were left off, inheritance would still be allowed.  This method of inheritance could be used for some type of internal class computation. However, any fields from the super class (called a Non-Entity Class in the JPA 2.0 documentation) would not be persisted.  Similarly, any listener methods (those annotated with @PreUpdate or @PostPersist) would not be called.

Extending GenericModel

In the Play Framework, it's important that JPA classes extend the GenericModel to keep with the ActiveRecord design and with the flow of the application.

This class, Tutorial, is the implementation of the persistance object for the BD_TUTORIAL table.

package models;

import java.util.*;
import javax.persistence.*;

import play.db.jpa.*;
import play.data.validation.*;

@Entity
@Table(name="bd_tutorial")
public class
Tutorial extends AbstractVersionedModel {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long tutorial_id;

   @MaxSize(100)
  @Required
  public String tutorial_title_tx;

  @Lob
  public String tutorial_desc_tx;

  @ManyToOne
  @JoinColumn(name="topic_cd")
  public Topic topic;
 

  public Integer display_seq_nb;

  public Boolean public_fl;

  @ManyToOne
  @JoinColumn(name="creator_account_id")
  public Account creator;

  @OneToMany(mappedBy="tutorial")
  List<TextualProc> textualProcs;

  public Tutorial() {
   
super();
  }

  public Tutorial(String tutorial_title_tx,
   String tutorial_desc_tx,
  T opic topic,
   Integer display_seq_nb,
   Boolean public_fl,
   Account creator
   ) {
   this();
   this.tutorial_title_tx = tutorial_title_tx;
   this.tutorial_desc_tx = tutorial_desc_tx;
   this.topic = topic;
   this.display_seq_nb = display_seq_nb;
   this.public_fl = public_fl;
   this.creator = creator;
  }

}

Here is the listing for AbstractVersionedModel.  An abstract class is used since it will never be instantiated on its own.  There is no table containing only the three columns CREATE_DT, LAST_UPDATE_DT, and VERSION.  AbstractVersionModel is a superclass of TextualProc and Video which persist the BD_TEXTUAL_PROC and BD_VIDEO tables, respectively.

package models;

import java.util.*;
import javax.persistence.*;

import play.db.jpa.*;

import play.data.validation.*;

@MappedSuperclass
abstract public class AbstractVersionedModel extends GenericModel {

  public Date create_dt;
  public Date last_update_dt;

  @Version
  public Integer version;

  public AbstractVersionedModel() {
   this.setAuditInfo();
  }

  @PreUpdate
  protected void setAuditInfo() {
   Date today_d = new Date();
   if( this.create_dt == null )
   this.create_dt = today_d;
   this.last_update_dt = today_d;
  }

}

AbstractVersionedModel uses a JPA "trigger" to set the required columns CREATE_DT and LAST_UPDATE_DT.  This is to ensure that they are set in a consistent manner regardless of the subclass.

Inheritance

Inheritance is a powerful concept but a very limiting one.  Because almost all of the tables in the database design described in the post have a common set of auditing and versioning columns -- and because these columns will be there for the life of the app -- it's worthwhile to factor these into a super class using the @MappedSuperclass annotation.  Care should be taken not to overuse inheritance as it's difficult to change later.  So, volatile relationships should be modeled using another technique like a "type" field or a @OneToOne.



1 comment: