Jan 7, 2013

Play! Framework: Database First!

I've known about Play!, the Java framework, for a couple of years now. I've played with it a few times going through their tutorial and documentation, but hadn't been serious about using it for a personal project until now.

The majority of their documentation and their tutorials describe how to develop an application with the code-first approach, but I've always been fond of creating applications with the database-first/model-first approach. I feel that it gives you better control of your data and ensures that your data set is completely normalized and devoid of redundant data. That being said, I found myself repeatedly hitting road blocks with this approach which almost led me to reconsider my choice of framework. But I endured because I see a lot of positive things in Play! (mainly the lack of a compile/build cycle). I ended up using Hibernate Tools to reverse engineer the model classes, but it took some tweaking.

I'm writing this post as a reminder to myself of all of the necessary steps I took, so that I can avoid hitting any road blocks in the future. Enjoy.

Gotchas to Remember

  1. All members in Play model classes must be public
  2. Getters and setters are implied (generated at runtime, but can be explicitly defined if need be)
  3. Hibernate annotations must be at the member-level and not at the getter-level
  4. All Play model classes that have explicit ID fields must extend GenericModel

General Steps

  1. Modify Hibernate Tools freemarker templates to move annotations to the members and not the getters
  2. Reverse engineer the mapper files (hbm.xml) from the database using Hibernate Tools with the newly modified templates
  3. Add meta tags to the mapper files to extend GenericModel, add imports, etc.
  4. Generate POJOs from the newly modified mapper files

Modify Hibernate Tools FreeMarker Templates to Move Annotations

  1. Extract "hibernate-tools.jar" to a temp directory (e.g. C:\tmp\hibernateForPlay).
  2. Within C:\tmp\hibernateForPlay\template\pojo, create a file named "Ejb3FieldGetAnnotation.ftl"

    This is actually a copy of "Ejb3PropertyGetAnnotation.ftl", but all instances of the word "property" are replaced by "field". This template will be placed in a loop that iterates through all fields instead of properties.

    The resulting file looks something like:

    <#if ejb3>
    <#if pojo.hasIdentifierProperty()>
    <#if field.equals(clazz.identifierProperty)>
     ${pojo.generateAnnIdGenerator()}
    <#-- if this is the id field (getter)-->
    <#-- explicitly set the column name for this field-->
    
    
    
    <#if c2h.isOneToOne(field)>
    ${pojo.generateOneToOneAnnotation(field, cfg)}
    <#elseif c2h.isManyToOne(field)>
    ${pojo.generateManyToOneAnnotation(field)}
    <#--TODO support optional and targetEntity-->    
    ${pojo.generateJoinColumnsAnnotation(field, cfg)}
    <#elseif c2h.isCollection(field)>
    ${pojo.generateCollectionAnnotation(field, cfg)}
    <#else>
    ${pojo.generateBasicAnnotation(field)}
    ${pojo.generateAnnColumnAnnotation(field)}
    
    
    
  3. Remove property-level annotations by commenting out the following line from "PojoPropertyAccessors.ftl"
    <#include "GetPropertyAnnotation.ftl"/>
  4. Add field-level annotations by adding the following include statement directly before the specified, existing line about getting field modifiers in "PojoFields.ftl"
    <#include "Ejb3FieldGetAnnotation.ftl"/>
    ${pojo.getFieldModifiers(field)}

    The resulting file looks something like:

    <#-- // Fields -->
    
    <#foreach field in pojo.getAllPropertiesIterator()><#if pojo.getMetaAttribAsBool(field, "gen-property", true)> <#if pojo.hasMetaAttribute(field, "field-description")>    /**
         ${pojo.getFieldJavaDoc(field, 0)}
         */
     
         <#include "Ejb3FieldGetAnnotation.ftl"/>
         ${pojo.getFieldModifiers(field)} ${pojo.getJavaTypeName(field, jdk5)} ${field.name}<#if pojo.hasFieldInitializor(field, jdk5)> = ${pojo.getFieldInitialization(field, jdk5)};
    
    
    
  5. Remove getters and setters by commenting out the following line from "Pojo.ftl"
    <#include "PojoPropertyAccessors.ftl"/>
    
    Note: This is doing things the Play! way since the getters and setters are implied. If you'd like to keep them explicitly defined, skip this step.

Reverse-engineer the Mapper Files from the Database

  1. Connect to your database and create a Hibernate Configuration as described in the attached reference links.
  2. Within the Hibernate Code Generation Configuration screen make sure to check "Use custom templates (for custom file generation)" and specify the directory where you saved the custom free marker templates we created in the section above (e.g. C:\tmp\hibernateForPlay).
  3. Run the configuration with only the Hibernate XML Mappings (.hbm.xml) exporter selected.

Add Necessary Meta Tags to the Mapper Files

As described in the Hibernate Tools guide you can specify meta tags within mapper files to further control the POJO code generation. To address some of the Play! specific gotchas listed above we are going to add the following (somewhat self-documenting) meta tags to all of our mapper files:

    <meta attribute="extends">GenericModel</meta>
    <meta attribute="extra-import">play.db.jpa.GenericModel</meta>
    <meta attribute="extra-import">play.data.validation.Required</meta>
    <meta attribute="scope-field">public</meta>

These should all be added directly within the class tag of the hiberate-mapping tag.

Generate POJOs from the Newly Modified Mapper Files

  1. Within the Hibernate Code Generation screen uncheck "Reverse engineer from JDBC Connection" because we want to use our modified mapper files.
  2. Within the Hibernate perspective, right-click on your configuration and click Edit Configuration.
  3. Under the Mappings tab at the Edit Configuration screen add our modified mapper files, so that it uses those to generate our POJOs.
  4. Run the configuration with only the Domain code (.java) exporter selected.

References:

3 comments:

  1. I face the same issue. I like to design the database first, then create the application. In fact, I'm having trouble designing a nicely normalized database using the regular Play Framework. Question: Did you find this tweaking approach worth it?

    Mike Price

    ReplyDelete
    Replies
    1. Hi Mike,

      First off, thanks for checking out my blog!

      If I'm creating an application with a relational database in mind, I always prefer to go the database-first route (I feel a little differently if I'm considering a NoSQL database). There's always some effort required to reverse-engineer the model classes from the database schema regardless of the MVC you choose. It's just that Play's quirks regarding model classes require a little more effort. To answer your question, yes, I think the "hit refresh workflow" that Play offers is worth the extra time and effort up front.

      Out of curiosity, what specific problems are you running into when designing a normalized database with Play?

      Patrick Cornnell

      Delete
  2. Best Ways to Get From Golden Nugget Casino to The Cosmopolitan
    This means that Golden Nugget Hotel & Casino in Las 세종특별자치 출장마사지 Vegas (NV) has over 2,200 rooms 광주광역 출장안마 that 정읍 출장안마 feature a full casino. 서귀포 출장안마 It is 전라남도 출장마사지 an easy-to-find spot

    ReplyDelete