Showing posts with label maven. Show all posts
Showing posts with label maven. Show all posts

Sunday, May 20, 2012

RigaJUG - Demo 2 - JPA2

In the first demo the model was limited to 3 tables.
The second demo marks an evolution of our model. Now there are 10 tables.
This demo presents the track JPA2 of minuteproject.


Now we want to know:
  • Who translate what via translation request. 
  • What a user can speak and can translate.
In fact the model can be altered in multiple ways, here is just one possibility.
In the current diagram there are multiple many-to-many relationships
  • request_key
  • application_x_key
  • language_x_translator
  • language_x_speaker
And 2 (language_x_translator and language_x_speaker) link twice user to language...

This demo will illustrate:
  • Enrichment facilities and customisation
  • Generation for JPA2
  • Integration technics of resulting artefacts

Model source

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `tranxy` ;
CREATE SCHEMA IF NOT EXISTS `tranxy` DEFAULT CHARACTER SET latin1;
USE `tranxy` ;

DROP TABLE IF EXISTS `tranxy`.`traduction` ;
DROP TABLE IF EXISTS `tranxy`.`translation_key` ;
DROP TABLE IF EXISTS `tranxy`.`language` ;
-- -----------------------------------------------------
-- Table `tranxy`.`translation_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_key` (
  `id` BIGINT NOT NULL AUTO_INCREMENT ,
  `key_name` VARCHAR(200) NULL ,
  `description` VARCHAR(400) NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language` (
  `idlanguage` INT NOT NULL AUTO_INCREMENT ,
  `code` VARCHAR(45) NOT NULL ,
  `locale` VARCHAR(10) NOT NULL ,
  `description` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`idlanguage`) ,
  UNIQUE INDEX `code_UNIQUE` (`code` ASC) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`user`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`user` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`user` (
  `iduser` BIGINT NOT NULL AUTO_INCREMENT ,
  `first_name` VARCHAR(45) NOT NULL ,
  `last_name` VARCHAR(45) NOT NULL ,
  `email` VARCHAR(200) NULL ,
  PRIMARY KEY (`iduser`) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`translation`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation` (
  `id` BIGINT NOT NULL AUTO_INCREMENT ,
  `translation` VARCHAR(800) NULL ,
  `language_id` INT NOT NULL ,
  `Key_id` BIGINT NOT NULL ,
  `is_final` TINYINT NOT NULL ,
  `date_finalization` DATE NULL ,
  `translator_id` BIGINT NOT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `fk_traduction_language` (`language_id` ASC) ,
  INDEX `fk_traduction_Key1` (`Key_id` ASC) ,
  INDEX `fk_traduction_user1` (`translator_id` ASC) ,
  CONSTRAINT `fk_traduction_language`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_traduction_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_traduction_user1`
    FOREIGN KEY (`translator_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`application`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`application` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`application` (
  `idapplication` BIGINT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(100) NULL ,
  `description` VARCHAR(200) NULL ,
  `type` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`idapplication`) ,
  UNIQUE INDEX `name_UNIQUE` (`name` ASC) )
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`application_x_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`application_x_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`application_x_key` (
  `Key_id` BIGINT NOT NULL ,
  `application_id` BIGINT NOT NULL ,
  INDEX `fk_application_x_key_Key1` (`Key_id` ASC) ,
  INDEX `fk_application_x_key_application1` (`application_id` ASC) ,
  PRIMARY KEY (`Key_id`, `application_id`) ,
  CONSTRAINT `fk_application_x_key_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_application_x_key_application1`
    FOREIGN KEY (`application_id` )
    REFERENCES `tranxy`.`application` (`idapplication` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`translation_request`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation_request` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_request` (
  `idtranslation_request` BIGINT NOT NULL AUTO_INCREMENT ,
  `name` VARCHAR(45) NULL ,
  `request_date` DATE NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  `Key_id` BIGINT NOT NULL ,
  PRIMARY KEY (`idtranslation_request`) ,
  INDEX `fk_translation_request_user1` (`user_id` ASC) ,
  INDEX `fk_translation_request_Key1` (`Key_id` ASC) ,
  CONSTRAINT `fk_translation_request_user1`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_translation_request_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`request_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`request_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`request_key` (
  `translation_request_id` BIGINT NOT NULL ,
  `language_id` INT NOT NULL ,
  INDEX `fk_request_key_translation_request1` (`translation_request_id` ASC) ,
  PRIMARY KEY (`translation_request_id`, `language_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  CONSTRAINT `fk_request_key_translation_request1`
    FOREIGN KEY (`translation_request_id` )
    REFERENCES `tranxy`.`translation_request` (`idtranslation_request` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_request_key_language1`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language_x_translator`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language_x_translator` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language_x_translator` (
  `language_id` INT NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  PRIMARY KEY (`language_id`, `user_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  INDEX `fk_language_x_translator_user1` (`user_id` ASC) ,
  CONSTRAINT `fk_request_key_language10`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_language_x_translator_user1`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `tranxy`.`language_x_speaker`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language_x_speaker` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language_x_speaker` (
  `language_id` INT NOT NULL ,
  `user_id` BIGINT NOT NULL ,
  PRIMARY KEY (`language_id`, `user_id`) ,
  INDEX `fk_request_key_language1` (`language_id` ASC) ,
  INDEX `fk_language_x_translator_user1` (`user_id` ASC) ,
  CONSTRAINT `fk_request_key_language100`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_language_x_translator_user10`
    FOREIGN KEY (`user_id` )
    REFERENCES `tranxy`.`user` (`iduser` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Enrichment facilities and customisation

Minuteproject configuration allows to define naming conventions working globally and allow specific enrichment whose granularity is limited to a table or a field.

New configuration

TRANXY-JPA2-2.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="mysql" version="5.1.16" groupId="mysql"
     artifactId="mysql-connector-java"></driver>
    <dataSource>
     <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
     <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <primaryKeyPolicy oneGlobal="false" >
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <business-package default="tranxy">
     <condition type="package" startsWith="trans" result="translation"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <column-naming-convention type="apply-fix-primary-key-column-name-when-no-ambiguity" 
              default-value="ID"/>  
      <column-naming-convention type="apply-strip-column-name-suffix"
       pattern-to-strip="ID" />
      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
      <reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="true"/>
     </conventions>
          <entity name="language_x_translator">
              <field name="language_id" linkReferenceAlias="translating_language" linkToTargetEntity="LANGUAGE"/>
              <field name="user_id" linkReferenceAlias="translator" linkToTargetEntity="USER"/>
          </entity>
          <entity name="LANGUAGE_X_SPEAKER">
              <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                  linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
              <field name="user_id" linkReferenceAlias="speaker" linkToTargetEntity="USER"/>
          </entity>
          <entity name="APPLICATION" alias="registered application">
              <field name="TYPE" alias="obedience">
                  <property tag="checkconstraint" alias="application_type">
                      <property name="OPENSOURCE"/>
                      <property name="COPYRIGHT" />
                  </property>
              </field>
          </entity>
          <entity name="LANGUAGE" content-type="reference-data"/>
    </enrichment>
   </business-model>
  </model>
  <targets>
   <target refname="JPA2" fileName="mp-template-config-JPA2.xml"
    outputdir-root="../../dev/latvianjug/output/JPA2" templatedir-root="../../template/framework/jpa">
    <property name="add-querydsl" value="2.1.2"></property>
    <property name="add-jpa2-implementation" value="hibernate"></property>
    <property name="add-cache-implementation" value="ehcache"></property>
   </target>
            <target refname="CACHE-LIB" fileName="mp-template-config-CACHE-LIB.xml"
                templatedir-root="../../template/framework/cache">
            </target>
   <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
    templatedir-root="../../template/framework/bsla">
   </target>
  </targets>
 </configuration>
</generator-config>

Global conventions

Make database convention java-friendly.

Fix primary key variable

<column-naming-convention type="apply-fix-primary-key-column-name-when-no-ambiguity" 
    default-value="ID"/>
In Language class the variable + getter/setter are related to 'id' althought mapped to 'idlanguage'.
    @Id @Column(name="idlanguage" )
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

Give readable collection name

When there is no ambiguity (only one foreign key between 2 entities), there is the possibility to have the collection variable made of the name of the linked table plurialized.
<reference-naming-convention 
    type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
In TranslationKey
private Set <translation> translations = new HashSet<translation>(); 

Simplify many-to-many relationship

Simplify many-to-many relationship variable when there is an ambiguity. In fact language_x_translator and language_x_speaker both user and language. So it is by default not possible to add 'users' variable to language since there should be 2 'users' variable. And vice-versa for user entity where 'languages' collection variable would be duplicate. This means that by default the variable is ambiguous and its name is a combination of link table foreign key and the other-end table, which gives quite a complexe name to read. The convention below in combination to some enrichment offers the possibility to get simple names declaratively.
<reference-naming-convention type="apply-many-to-many-aliasing" is-to-plurialize="true"/>
Combined with many-to-many tables enrichment
                    <entity name="language_x_translator">
                        <field name="language_id" linkReferenceAlias="translating_language" linkToTargetEntity="LANGUAGE"/>
                        <field name="user_id" linkReferenceAlias="translator" linkToTargetEntity="USER"/>
                    </entity>
                    <entity name="LANGUAGE_X_SPEAKER">
                        <field name="LANGUAGE_ID" linkToTargetEntity="LANGUAGE"
                            linkToTargetField="IDLANGUAGE" linkReferenceAlias="spoken_language" />
                        <field name="user_id" linkReferenceAlias="speaker" linkToTargetEntity="USER"/>
                    </entity>
Gives for User entity
    @ManyToMany
    @JoinTable(name="LANGUAGE_X_SPEAKER", 
        joinColumns=@JoinColumn(name="user_id"), 
        inverseJoinColumns=@JoinColumn(name="language_id") 
    )
    private Set <language> spokenLanguages = new HashSet <Language> ();

    @ManyToMany
    @JoinTable(name="LANGUAGE_X_TRANSLATOR", 
        joinColumns=@JoinColumn(name="user_id"), 
        inverseJoinColumns=@JoinColumn(name="language_id") 
    )
    private Set <language> translatingLanguages = new HashSet <Language> ();
Gives for Language entity
    @ManyToMany
    @JoinTable(name="LANGUAGE_X_SPEAKER", 
        joinColumns=@JoinColumn(name="language_id"), 
        inverseJoinColumns=@JoinColumn(name="user_id") 
    )
    private Set <user> speakers = new HashSet <User> ();

    @ManyToMany
    @JoinTable(name="LANGUAGE_X_TRANSLATOR", 
        joinColumns=@JoinColumn(name="language_id"), 
        inverseJoinColumns=@JoinColumn(name="user_id") 
    )
    private Set <user> translators = new HashSet <User> ();

Local enrichment

Fine grain tuning: Granularity at the level of the entity or attribute.

Modify the name of the entity with alias

<entity name="APPLICATION" alias="registered application" >
Gives
@Entity (name="RegisteredApplication")
@Table (name="application")
public class RegisteredApplication ...

Modify the name of the field with alias

<field name="TYPE" alias="obedience" >
Gives
    @Column(name="type")
    private ApplicationType obedience;

Add enumeration

                        <field name="TYPE" alias="obedience" >
                            <property tag="checkconstraint" alias="application_type">
                                <property name="OPENSOURCE"/>
                                <property name="COPYRIGHT" />
                            </property>
                        </field>
Gives
    @Enumerated (EnumType.STRING)
    @Column(name="type")
    private ApplicationType obedience; 
And a Enum artefact
public enum ApplicationType {

    OPENSOURCE("OPENSOURCE"),
    COPYRIGHT("COPYRIGHT");

    private final String value;
...

Providing content type of an entity

<entity name="LANGUAGE" content-type="reference-data"/>
Gives an entry in ehcache.xml configuration
   <cache
    name="net.sf.mp.demo.tranxy.domain.tranxy.Language"
        maxElementsInMemory="5000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"
   />

Generation

Same as for demo one:
Put TRANXY-JPA2-2.xml in /mywork/config
Run
>model-generation.cmd TRANXY-JPA2-2.xml

But what happened to my altered code?

It is kept. Your validation annotation are not erased!

With Minuteproject one shot-generation as 'too-much-often' seen is over!
Be ready for continuous-refactoring of your backend!

Build and test

The model has changed and 2 new fields are mandatory to create a Language. The unit test is modified.
package mytest;

import javax.persistence.*;
import javax.validation.*;
import javax.validation.constraints.*;

import static junit.framework.Assert.*;

import org.junit.*;

import net.sf.mp.demo.tranxy.domain.tranxy.*;

public class TranxyTest {

 EntityManagerFactory emf = Persistence.createEntityManagerFactory("tranxy");
 EntityManager em = emf.createEntityManager();
 
 @Test
 public void testLanguage() {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Language language = new Language();
  language.setCode("FR");
  
  //demo2
  language.setDescription("France");
  language.setLocale("fr");
  
  em.persist(language);
  tx.commit();
 }
 
    @Test
    public void testTooSmallLanguage() {

        try {
      EntityTransaction tx = em.getTransaction(); 
      tx.begin();
      Language language = new Language();
      language.setCode("F");
      em.persist(language);
            fail("Expected ConstraintViolationException wasn't thrown.");
            tx.commit();
        } 
        catch (ConstraintViolationException e) {
            assertEquals(1, e.getConstraintViolations().size());
            ConstraintViolation violation = 
                e.getConstraintViolations().iterator().next();

            assertEquals("code", violation.getPropertyPath().toString());
            assertEquals(
                Size.class, 
                violation.getConstraintDescriptor().getAnnotation().annotationType());
        }
    } 
 
 @Before
 public void clean () {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Query q = em.createQuery("delete Language");
  int i = q.executeUpdate();
  tx.commit();
 }
}

To build run
>mvn clean package

Integration technics

Two are standard
  • integration by extension
  • integration by overriding
Minuteproject adds a new one
  • Integration by alteration/mutation
  • 3 types of alteration
    • artifact level (exclude for next generation)
    • snippet level
      • added part
      • updatable part

Download

The result can be downloaded on google code minuteproject.







RigaJUG - Demo 1 - JPA2

First Demo of RigaJUG agenda.
This demo presents the track JPA2 of minuteproject. 

Starting from a simple model that holds translation information.
Minuteproject will generate a JPA2 layer.

This demo will illustrate:
  • Generation by console or via config file
  • Altering generated code to add JSR 303 (validation) annotation.
  • Writing a unit test
  • Enrichment facilities and customisation
Prerequisits
  • Download Minuteproject last version
  • Java 6 in path
  • Maven in path
  • Install model on myql
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

DROP SCHEMA IF EXISTS `tranxy` ;
CREATE SCHEMA IF NOT EXISTS `tranxy` DEFAULT CHARACTER SET latin1 ;
USE `tranxy` ;

-- -----------------------------------------------------
-- Table `tranxy`.`translation_key`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`translation_key` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`translation_key` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT ,
  `key_name` VARCHAR(200) NULL DEFAULT NULL ,
  `description` VARCHAR(400) NULL DEFAULT NULL ,
  PRIMARY KEY (`id`) )
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `tranxy`.`language`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`language` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`language` (
  `idlanguage` INT(11) NOT NULL AUTO_INCREMENT ,
  `code` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`idlanguage`) ,
  UNIQUE INDEX `code_UNIQUE` (`code` ASC) )
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `tranxy`.`traduction`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `tranxy`.`traduction` ;

CREATE  TABLE IF NOT EXISTS `tranxy`.`traduction` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT ,
  `translation` VARCHAR(800) NULL DEFAULT NULL ,
  `language_id` INT(11) NOT NULL ,
  `Key_id` BIGINT(20) NOT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `fk_traduction_language` (`language_id` ASC) ,
  INDEX `fk_traduction_Key1` (`Key_id` ASC) ,
  CONSTRAINT `fk_traduction_Key1`
    FOREIGN KEY (`Key_id` )
    REFERENCES `tranxy`.`translation_key` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_traduction_language`
    FOREIGN KEY (`language_id` )
    REFERENCES `tranxy`.`language` (`idlanguage` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;



SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

Console generation

By console

In /application run
>start-console.cmd/sh
Fill the 'Data model reverse-engineering' tab
And click on generate

The output goes in /output/trans/JPA2
To build the resulting package execute
>mvn clean package

By command line

Add configuration in /mywork/config
TRANXY-JPA2-1.xml
<!DOCTYPE root>
<generator-config xmlns="http://minuteproject.sf.net/xsd/mp-config" 
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" 
xs:noNamespaceSchemaLocation="../config/mp-config.xsd">
 <configuration>
  <conventions>
   <target-convention type="enable-updatable-code-feature" />
  </conventions>
  <model name="tranxy" version="1.0" package-root="net.sf.mp.demo">
   <data-model>
    <driver name="mysql" version="5.1.16" groupId="mysql"
     artifactId="mysql-connector-java"></driver>
    <dataSource>
     <driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
     <url>jdbc:mysql://127.0.0.1:3306/tranxy</url>
     <username>root</username>
     <password>mysql</password>
    </dataSource>
    <primaryKeyPolicy oneGlobal="false" >
     <primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
    </primaryKeyPolicy>
   </data-model>
   <business-model>
    <business-package default="tranxy">
     <condition type="package" startsWith="trans" result="translation"></condition>
    </business-package>
    <enrichment>
     <conventions>
      <!-- manipulate the structure and entities BEFORE manipulating the 
       entities -->
      <column-naming-convention type="apply-strip-column-name-suffix"
       pattern-to-strip="ID" />
      <reference-naming-convention
       type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
     </conventions>
    </enrichment>
   </business-model>
  </model>
  <targets>
   <target refname="JPA2" fileName="mp-template-config-JPA2.xml"
    outputdir-root="../../dev/latvianjug/output/JPA2" templatedir-root="../../template/framework/jpa">
    <property name="add-querydsl" value="2.1.2"></property>
    <property name="add-jpa2-implementation" value="hibernate"></property>
    <property name="environment" value="remote"></property>
   </target>
      <target refname="CACHE-LIB" fileName="mp-template-config-CACHE-LIB.xml"
          templatedir-root="../../template/framework/cache">
      </target>
   <target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
    templatedir-root="../../template/framework/bsla">
   </target>
  </targets>
 </configuration>
</generator-config>

Execute by running:
> model-generation.cmd/sh TRANXY-JPA2-1.xml
The ouput goes in /dev/latvianjug/output/JPA2

Make a build:
>mvn clean package

Console vs. Command line generation

The generation made by the command line has more enrichment facilities than just working via the console.
Example: here the configuration add querydsl integration.
The rest of the demo will focus on the configuration enrichment facilities.
Remark:
Ideally the console should move into a IDE plugin manipulating the configuration.

Resulting artefacts

Summary
  • A maven pom project
  • 3 JPA2 entities (one for each table)
Pom artefact
  • provide a jar with name and version number given in configuration
  • has hibernate, querydsl, mysql driver, junit dependencies
JPA2 entities
  • packaged logically entity starting with 'trans' goes to package translation other goes to 'tranxy' package
  • convention 'apply-strip-column-name-suffix' when ending with 'ID'
    • DB naming convention used (the foreign key name is composed of the name of the link entity + '_id' is converted into java by provided name stripped from the 'Id' particule.
  • primary key strategy:
    • Based on auto increment pk when not natural.
JPA2 metamodel
  • each entity is associated to a metamodel java file to build type safe criteria queries.
persistence.xml: 2 are generated.
MinuteProject artefacts are designed to be tested and ready to be deployed!
  • in src/main/resources/META-INF
    • with reference to a JTA datasource
  • in src/test/resources/META-INF
    • with reference to an embedded Connection pool
Global convention
All artefacts have an updatable nature, meaning that you can change the code or add new code and consecutive generation will keep your modifications.

Code

Translation key
/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : DomainEntityJPA2Annotation
 * - file name : DomainEntityJPA2Annotation.vm
*/
package net.sf.mp.demo.tranxy.domain.translation;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
import java.sql.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import java.io.Serializable;
import javax.persistence.*;
import net.sf.mp.demo.tranxy.domain.tranxy.Traduction;

/**
 *
 * <p>Title: TranslationKey</p>
 *
 * <p>Description: Domain Object describing a TranslationKey entity</p>
 *
 */
@Entity (name="TranslationKey")
@Table (name="translation_key")
@NamedQueries({
  @NamedQuery(name="TranslationKey.findAll", query="SELECT translationKey FROM TranslationKey translationKey")
 ,@NamedQuery(name="TranslationKey.findByKeyName", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.keyName = :keyName")
 ,@NamedQuery(name="TranslationKey.findByKeyNameContaining", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.keyName like :keyName")
 ,@NamedQuery(name="TranslationKey.findByDescription", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.description = :description")
 ,@NamedQuery(name="TranslationKey.findByDescriptionContaining", query="SELECT translationKey FROM TranslationKey translationKey WHERE translationKey.description like :description")
})
public class TranslationKey implements Serializable {
    private static final long serialVersionUID = 1L;
 
    public static final String FIND_ALL = "TranslationKey.findAll";
    public static final String FIND_BY_KEYNAME = "TranslationKey.findByKeyName";
    public static final String FIND_BY_KEYNAME_CONTAINING ="TranslationKey.findByKeyNameContaining";
    public static final String FIND_BY_DESCRIPTION = "TranslationKey.findByDescription";
    public static final String FIND_BY_DESCRIPTION_CONTAINING ="TranslationKey.findByDescriptionContaining";
 
    @Id @Column(name="id" )
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

//MP-MANAGED-ADDED-AREA-BEGINNING @key_name-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @key_name-field-annotation@
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-key_name@
    @Column(name="key_name",  length=200,  nullable=true,  unique=false)
    private String keyName; 
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-ADDED-AREA-BEGINNING @description-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @description-field-annotation@
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-description@
    @Column(name="description",  length=400,  nullable=true,  unique=false)
    private String description; 
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-field-translation_key@
    @OneToMany (targetEntity=net.sf.mp.demo.tranxy.domain.tranxy.Traduction.class, fetch=FetchType.LAZY, mappedBy="key", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
    private Set <Traduction> traductions = new HashSet<Traduction>(); 

//MP-MANAGED-UPDATABLE-ENDING
    /**
    * Default constructor
    */
    public TranslationKey() {
    }

 /**
 * All field constructor 
 */
    public TranslationKey(
       Long id,
       String keyName,
       String description) {
       //primary keys
       setId (id);
       //attributes
       setKeyName (keyName);
       setDescription (description);
       //parents
    }

 public TranslationKey flat() {
    return new TranslationKey(
          getId(),
          getKeyName(),
          getDescription()
    );
 }

    public Long getId() {
        return id;
    }
 
    public void setId (Long id) {
        this.id =  id;
    }
    
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-key_name@
    public String getKeyName() {
        return keyName;
    }
 
    public void setKeyName (String keyName) {
        this.keyName =  keyName;
    }    
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-description@
    public String getDescription() {
        return description;
    }
 
    public void setDescription (String description) {
        this.description =  description;
    }    
//MP-MANAGED-UPDATABLE-ENDING



//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-getter-translation_key@
    public Set<Traduction> getTraductions() {
        if (traductions == null){
            traductions = new HashSet<Traduction>();
        }
        return traductions;
    }

    public void setTraductions (Set<Traduction> traductions) {
        this.traductions = traductions;
    } 
    
    public void addTraductions (Traduction traduction) {
         getTraductions().add(traduction);
    }
    
//MP-MANAGED-UPDATABLE-ENDING


//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}
Language
/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : DomainEntityJPA2Annotation
 * - file name : DomainEntityJPA2Annotation.vm
*/
package net.sf.mp.demo.tranxy.domain.tranxy;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
import java.sql.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import java.io.Serializable;
import javax.persistence.*;
import net.sf.mp.demo.tranxy.domain.tranxy.Traduction;

/**
 *
 * <p>Title: Language</p>
 *
 * <p>Description: Domain Object describing a Language entity</p>
 *
 */
@Entity (name="Language")
@Table (name="language")
@NamedQueries({
  @NamedQuery(name="Language.findAll", query="SELECT language FROM Language language")
 ,@NamedQuery(name="Language.findByCode", query="SELECT language FROM Language language WHERE language.code = :code")
 ,@NamedQuery(name="Language.findByCodeContaining", query="SELECT language FROM Language language WHERE language.code like :code")
})
public class Language implements Serializable {
    private static final long serialVersionUID = 1L;
 
    public static final String FIND_ALL = "Language.findAll";
    public static final String FIND_BY_CODE = "Language.findByCode";
    public static final String FIND_BY_CODE_CONTAINING ="Language.findByCodeContaining";
 
    @Id @Column(name="idlanguage" )
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer idlanguage;

//MP-MANAGED-ADDED-AREA-BEGINNING @code-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @code-field-annotation@
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-code@
    @Column(name="code",  length=45, nullable=false,  unique=false)
    private String code; 
//MP-MANAGED-UPDATABLE-ENDING

//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-field-language@
    @OneToMany (targetEntity=net.sf.mp.demo.tranxy.domain.tranxy.Traduction.class, fetch=FetchType.LAZY, mappedBy="language", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
    private Set <Traduction> traductions = new HashSet<Traduction>(); 

//MP-MANAGED-UPDATABLE-ENDING
    /**
    * Default constructor
    */
    public Language() {
    }

 /**
 * All field constructor 
 */
    public Language(
       Integer idlanguage,
       String code) {
       //primary keys
       setIdlanguage (idlanguage);
       //attributes
       setCode (code);
       //parents
    }

 public Language flat() {
    return new Language(
          getIdlanguage(),
          getCode()
    );
 }

    public Integer getIdlanguage() {
        return idlanguage;
    }
 
    public void setIdlanguage (Integer idlanguage) {
        this.idlanguage =  idlanguage;
    }
    
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-code@
    public String getCode() {
        return code;
    }
 
    public void setCode (String code) {
        this.code =  code;
    }    
//MP-MANAGED-UPDATABLE-ENDING



//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @traductions-getter-language@
    public Set<Traduction> getTraductions() {
        if (traductions == null){
            traductions = new HashSet<Traduction>();
        }
        return traductions;
    }

    public void setTraductions (Set<Traduction> traductions) {
        this.traductions = traductions;
    } 
    
    public void addTraductions (Traduction traduction) {
         getTraductions().add(traduction);
    }
    
//MP-MANAGED-UPDATABLE-ENDING


//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}
Tranduction
/**
 * Copyright (c) minuteproject, minuteproject@gmail.com
 * All rights reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * More information on minuteproject:
 * twitter @minuteproject
 * wiki http://minuteproject.wikispaces.com 
 * blog http://minuteproject.blogspot.net
 * 
*/
/**
 * template reference : 
 * - name : DomainEntityJPA2Annotation
 * - file name : DomainEntityJPA2Annotation.vm
*/
package net.sf.mp.demo.tranxy.domain.tranxy;

//MP-MANAGED-ADDED-AREA-BEGINNING @import@
//MP-MANAGED-ADDED-AREA-ENDING @import@
import java.sql.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import java.io.Serializable;
import javax.persistence.*;
import net.sf.mp.demo.tranxy.domain.translation.TranslationKey;
import net.sf.mp.demo.tranxy.domain.tranxy.Language;

/**
 *
 * <p>Title: Traduction</p>
 *
 * <p>Description: Domain Object describing a Traduction entity</p>
 *
 */
@Entity (name="Traduction")
@Table (name="traduction")
@NamedQueries({
  @NamedQuery(name="Traduction.findAll", query="SELECT traduction FROM Traduction traduction")
 ,@NamedQuery(name="Traduction.findByTranslation", query="SELECT traduction FROM Traduction traduction WHERE traduction.translation = :translation")
 ,@NamedQuery(name="Traduction.findByTranslationContaining", query="SELECT traduction FROM Traduction traduction WHERE traduction.translation like :translation")
})
public class Traduction implements Serializable {
    private static final long serialVersionUID = 1L;
 
    public static final String FIND_ALL = "Traduction.findAll";
    public static final String FIND_BY_TRANSLATION = "Traduction.findByTranslation";
    public static final String FIND_BY_TRANSLATION_CONTAINING ="Traduction.findByTranslationContaining";
 
    @Id @Column(name="id" )
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

//MP-MANAGED-ADDED-AREA-BEGINNING @translation-field-annotation@
//MP-MANAGED-ADDED-AREA-ENDING @translation-field-annotation@
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-translation@
    @Column(name="translation",  length=800,  nullable=true,  unique=false)
    private String translation; 
//MP-MANAGED-UPDATABLE-ENDING

    @ManyToOne (fetch=FetchType.LAZY , optional=false)
    @JoinColumn(name="Key_id", referencedColumnName = "id", nullable=false,  unique=false ) 
    private TranslationKey key;  

    @Column(name="Key_id",  nullable=false,  unique=false, insertable=false, updatable=false)
    private Long key_;

    @ManyToOne (fetch=FetchType.LAZY , optional=false)
    @JoinColumn(name="language_id", referencedColumnName = "idlanguage", nullable=false,  unique=false ) 
    private Language language;  

    @Column(name="language_id",  nullable=false,  unique=false, insertable=false, updatable=false)
    private Integer language_;

    /**
    * Default constructor
    */
    public Traduction() {
    }

 /**
 * All field constructor 
 */
    public Traduction(
       Long id,
       String translation,
       Integer language,
       Long key) {
       //primary keys
       setId (id);
       //attributes
       setTranslation (translation);
       //parents
       this.key = new TranslationKey();
       this.key.setId(key); //ID
       this.language = new Language();
       this.language.setIdlanguage(language); //IDLANGUAGE
    }

 public Traduction flat() {
    return new Traduction(
          getId(),
          getTranslation(),
          getLanguage_(),
          getKey_()
    );
 }

    public Long getId() {
        return id;
    }
 
    public void setId (Long id) {
        this.id =  id;
    }
    
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @GETTER-SETTER-translation@
    public String getTranslation() {
        return translation;
    }
 
    public void setTranslation (String translation) {
        this.translation =  translation;
    }    
//MP-MANAGED-UPDATABLE-ENDING


    public TranslationKey getKey () {
     return key;
    }
 
    public void setKey (TranslationKey key) {
     this.key = key;
    }

    public Long getKey_() {
        return key_;
    }
 
    public void setKey_ (Long key) {
        this.key_ =  key;
    }
 
    public Language getLanguage () {
     return language;
    }
 
    public void setLanguage (Language language) {
     this.language = language;
    }

    public Integer getLanguage_() {
        return language_;
    }
 
    public void setLanguage_ (Integer language) {
        this.language_ =  language;
    }

//MP-MANAGED-ADDED-AREA-BEGINNING @implementation@
//MP-MANAGED-ADDED-AREA-ENDING @implementation@

}
persistence.xml in test directory
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
             version="1.0">
<!--MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @PERSISTENCE-UNIT-tranxy@-->
    <persistence-unit name="tranxy" transaction-type="RESOURCE_LOCAL">
<!--MP-MANAGED-UPDATABLE-ENDING-->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <!-- tranxy --> 
        <class>net.sf.mp.demo.tranxy.domain.tranxy.Language</class>
        <class>net.sf.mp.demo.tranxy.domain.tranxy.Traduction</class>
        <!-- translation --> 
        <class>net.sf.mp.demo.tranxy.domain.translation.TranslationKey</class>
        <properties>
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.connection.driver_class" value="org.gjt.mm.mysql.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://127.0.0.1:3306/tranxy" />
            <property name="hibernate.connection.username" value="root" />
            <property name="hibernate.connection.password" value="mysql" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
        </properties> 
    </persistence-unit>
</persistence>

Persistence.xml in main directory

The persistence.xml provides a configuration with embedded connection pool.
If the user want to use a remote connection pool accessed by JNDI, the user has to put property

lt;property name="environment" value="remote" />
Under the target JPA2 node.

Alter Generated Code and Unit Test

With Minuteproject one shot-generation as 'too-much-often' seen is over!
Be ready for continuous-refactoring of your backend!

Add a validation annotation

In Language class
//MP-MANAGED-ADDED-AREA-BEGINNING @import@
import javax.validation.constraints.*;
//MP-MANAGED-ADDED-AREA-ENDING @import@

...
//MP-MANAGED-ADDED-AREA-BEGINNING @code-field-annotation@
    @Size(min = 2)
//MP-MANAGED-ADDED-AREA-ENDING @code-field-annotation@
//MP-MANAGED-UPDATABLE-BEGINNING-DISABLE @ATTRIBUTE-code@
    @Column(name="code",  length=45, nullable=false,  unique=false)
    private String code; 

Add the imports between the MP-MANAGED-ADDED-AREA-BEGINNING @import@ and MP-MANAGED-ADDED-AREA-ENDING @import@ comments
Add the annotation between MP-MANAGED-ADDED-AREA-BEGINNING @xxxx-field-annotation@ and MP-MANAGED-ADDED-AREA-ENDING @xxxx-field-annotation@ comments

Now further generation will keep your added code.

Unit test

In src/test/java add the TranxyTest class in package mytest
The validation scenario has been inspired by this link.

package mytest;

import javax.persistence.*;
import javax.validation.*;
import javax.validation.constraints.*;

import static junit.framework.Assert.*;

import org.junit.*;

import net.sf.mp.demo.tranxy.domain.tranxy.*;

public class TranxyTest {

 EntityManagerFactory emf = Persistence.createEntityManagerFactory("tranxy");
 EntityManager em = emf.createEntityManager();
 
 @Test
 public void testLanguage() {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Language language = new Language();
  language.setCode("FR");
  em.persist(language);
  tx.commit();
 }
 
    @Test
    public void testTooSmallLanguage() {

        try {
      EntityTransaction tx = em.getTransaction(); 
      tx.begin();
      Language language = new Language();
      language.setCode("F");
      em.persist(language);
            fail("Expected ConstraintViolationException wasn't thrown.");
            tx.commit();
        } 
        catch (ConstraintViolationException e) {
            assertEquals(1, e.getConstraintViolations().size());
            ConstraintViolation violation = 
                e.getConstraintViolations().iterator().next();
            assertEquals("code", violation.getPropertyPath().toString());
            assertEquals(
                Size.class, 
                violation.getConstraintDescriptor().getAnnotation().annotationType());
        }
    } 
 
 @Before
 public void clean () {
  EntityTransaction tx = em.getTransaction(); 
  tx.begin();
  Query q = em.createQuery("delete Language");
  q.executeUpdate();
  tx.commit();
 }
}


Execute with
>mvn clean package
The 2 tests pass.

Download


The result can be downloaded on google code minuteproject.

Monday, June 27, 2011

MinuteProject 4 JPA2: Lazuly showcase

Intro
The goal of this article is to have a working JPA2 layer on top of a data model by writting 0 (yes ZERO) lines of code.
Lazuly-db is a opensource database hosted on googlecode that represents the structure of a database holding conference related informations.
Lazuly-db will be used as a showcase of various MinuteProject technology tracks.
In version 0.1 it has 13 tables, 2 views.

This article explains the different steps to quickly have a working JPA2 backend.
It is furthermore a tutorial on how to enrich the database model to get customized artifacts providing specific behaviour.
The purpose is to give to user enough material to customize it and go on extending your model.

MinuteProject short story
The idea behind minuteproject is to
  • go fast;
  • reduce the technology learning curve by knowledge cristalization;
  • improve the quality of the artifact;
  • shorten drastically the time to market of the product.
Minuteproject acts as a Productivity provider targeting multiple aspects of the reverse-engineering for a target architecture (here JPA2).
While most of existing reverse-engineering solution focus on one part of your architecture (Domain Object), MinuteProject can generate artifacts related to:
  • columns;
  • entities (tables,views);
  • stored procedures;
  • package;
  • model;
  • application.
It allows the developer to achieve 'reproductability' that is a great complement of reusability.
Reusability allows to bundle common behavior into a package/framework/specification in order to use them but not to code them.
Reproductability allows to reach the same quality of artifacts (config/classes...) matching a target architecture (reused) although the context changes (DB model).

JPA2 track
JPA2 is a JEE specification for persisting objects in a database.
MinuteProject 4 JPA2 (release 0.5.5) offers a reverse-engineering solution targeting JPA2 artifacts:
  • Java Entity classes for tables and views;
  • JPA2 metamodel;
  • persistence xml;
  • maven pom.xml;
  • vendor specific implementation;
  • querydsl integration.
More information can be found at minuteproject4jpa2

Operating principle
MinuteProject works with a configuration file.
This file holds the information regarding the model and how to enrich it. It also specifies the target bundle of templates for a technology track.

Datasource
Lazuly DB main entities: The schema below resumes the lazuly DB main entities (tables/views) and theirs relationships.


This part summarizes the different configurations used:

Here is the sample xml configuration that is applied to the model
<!DOCTYPE root>
<generator-config>
<configuration>
<model name="conference" version="1.0" package-root="net.sf.mp.demo">
<!-- <model name="lazuly" version="1.0" package-root="mp.demo"> -->
<data-model>
<driver name="mysql" version="5.1.16" groupId="mysql"
artifactId="mysql-connector-java"></driver>
<dataSource>
<driverClassName>org.gjt.mm.mysql.Driver</driverClassName>
<url>jdbc:mysql://127.0.0.1:3306/conference</url>
<username>root</username>
<password>mysql</password>
</dataSource>
<!-- for Oracle and DB2 please set the schema <schema> </schema> -->
<primaryKeyPolicy oneGlobal="true">
<primaryKeyPolicyPattern name="autoincrementPattern"></primaryKeyPolicyPattern>
</primaryKeyPolicy>
</data-model>
<business-model>
<!-- <generation-condition> <condition type="exclude" startsWith="DUAL"></condition>
</generation-condition> -->
<business-package default="conference">
<condition type="package" startsWith="STAT" result="statistics"></condition>
<condition type="package" startsWith="COUNTRY" result="admin"></condition>
<condition type="package" startsWith="ROLE" result="admin"></condition>
</business-package>
<enrichment>
<conventions>
<column-naming-convention type="apply-strip-column-name-suffix"
pattern-to-strip="_ID" />
<reference-naming-convention
type="apply-referenced-alias-when-no-ambiguity" is-to-plurialize="true" />
</conventions>

<entity name="PRESENTATION">
<field name="STATUS">
<property tag="checkconstraint" alias="presentation_status">
<property name="PROPOSAL" value="PROPOSAL" />
<property name="ACTIVE" value="ACTIVE" />
</property>
</field>
</entity>
<entity name="SPONSOR">
<field name="STATUS">
<property tag="checkconstraint" alias="sponsor_status">
<property name="PENDING" value="PENDING" />
<property name="ACTIVE" value="ACTIVE" />
</property>
</field>
<field name="PRIVILEGE_TYPE">
<property tag="checkconstraint" alias="sponsor_privilege">
<property name="GOLDEN" value="Golden" />
<property name="SILVER" value="Silver" />
<property name="BRONZE" value="Bronze" />
</property>
</field>
</entity>
<!-- views -->
<entity name="stat_mb_per_ctry_conf" alias="MEMBER_PER_COUNTRY_AND_CONFERENCE">
<virtual-primary-key isRealPrimaryKey="true">
<property name="virtualPrimaryKey" value="ID" />
</virtual-primary-key>
</entity>
<entity name="stat_mb_by_role" alias="MEMBER_PER_ROLE_COUNTRY_AND_CONFERENCE">
<virtual-primary-key isRealPrimaryKey="true">
<property name="virtualPrimaryKey" value="id" />
</virtual-primary-key>
<field name="stat_mb_per_ctry_conf_ID" linkToTargetEntity="stat_mb_per_ctry_conf"
linkToTargetField="id"></field>
</entity>
</enrichment>
</business-model>
</model>
<targets>

<target refname="JPA2" fileName="mp-template-config-JPA2.xml"
outputdir-root="../../dev/output/JPA2" templatedir-root="../../template/framework/jpa">
<property name="add-querydsl" value="2.1.2"></property>
<property name="add-jpa2-implementation" value="hibernate"></property>
</target>

<target refname="LIB" fileName="mp-template-config-bsla-LIB-features.xml"
templatedir-root="../../template/framework/bsla">
</target>

</targets>
</configuration>
</generator-config>
The configuration provides:
  • A model name: conference, version and package root;
  • the driver as maven reference;
  • connection parameters;
  • primarykey policy (autoincrement)
  • business packages: entities starting with stat go to 'statistics'...;role, countries goes in administration package
  • convention: strip-column-name-suffix _ID (by convention in the database, I have column naming ending with '_ID' for foreign key. Here the Java naming convention can defer from the DB naming
  • convention: apply-referenced-alias-when-no-ambiguity indicating that when a table has a one2many relationship with other (i.e. has child), if it has not 2 children with the same other side table (no ambiguity) than the name of the variable is based on the other side table, and plurialized (english) collections
  • Entity enrichment: name alias (can have a distinct Java class name than the one derived from the DB);
  • Field enrichment: provide a check constraint that becomes a Java Enum
  • Entity (views) enrichment by providing them a primary key (create a unique key) by adding foreignkey.
The target part of the configuration specifies against which target technology, MinuteProject has to generate. Here the track is JPA2, where it is specified to use querydsl 2.1.2 and a JPA vendor implementation Hibernate.
Those indications are used to generate pom.xml and persistence.xml files.

Sources and installation
Lazuly project
Svn the source as describe on http://code.google.com/p/lazuly/source/checkout

Mysql
Install mysql
run conference-db.0.1.sql on schema conference

Minuteproject
Download and install minuteproject 0.5.5 or higher. Minuteproject is installed in MP_HOME.

Execution
Normally within couple of seconds (1 to 5) the code should be generated (If DB instance and minuteproject run is on same machine)

Alternate execution
  • Open minuteproject console %MP_HOME%/bin/start-console.(cmd/sh)
  • field the connection parameters
  • add package root and model name
  • click generate
This alternative generation works fine to give a first glance at what you can have as output. A better enrichment is available via the xml config as previously described.

Output
The output can be browsed at http://code.google.com/p/lazuly/source/browse/#svn%2Ftrunk%2Flazuly-jpa2

Here are some samples of what has been generated in terms of

Structure
Here the structure open with as an eclipse-maven project


Java class as JPA2 entity with enumeration, OneToMany, ManyToOne, ManyToMany
You can note that the field 'abstract' in the table 'presentation' is converted to 'abstractName' to avoid compilation error. This is true for all the java reserved keywords. The getter and setter are adapted accordingly.
package net.sf.mp.demo.conference.conference;

import java.sql.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import javax.persistence.*;

import net.sf.mp.demo.conference.conference.Evaluation;
import net.sf.mp.demo.conference.conference.PresentationPlace;
import net.sf.mp.demo.conference.conference.Speaker;
import net.sf.mp.demo.conference.enumeration.conference.PresentationStatusEnum;

/**
*
* <p>Title: Presentation</p>
*
* <p>Description: Domain Object describing a Presentation entity</p>
*
*/
@Entity (name="Presentation")
@Table (name="presentation")
public class Presentation {

@Id @Column(name="id" )
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Column(name="start_time", nullable=false, unique=false)
private Timestamp startTime;
@Column(name="end_time", nullable=false, unique=false)
private Timestamp endTime;
@Column(name="abstract", length=500, nullable=false, unique=false)
private String abstractName;
@Column(name="title", length=255, nullable=false, unique=false)
private String title;
@Enumerated (EnumType.STRING)
private PresentationStatusEnum status;
@Column(name="proposal_time", nullable=true, unique=false)
private Timestamp proposalTime;

@ManyToOne (fetch=FetchType.LAZY )
@JoinColumn(name="presentation_place_id", referencedColumnName = "id", nullable=true, unique=false )
private PresentationPlace presentationPlaceId;

@OneToMany (targetEntity=net.sf.mp.demo.conference.conference.Evaluation.class, fetch=FetchType.LAZY, mappedBy="presentationId", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
private Set <Evaluation> evaluations = new HashSet<Evaluation>();

@ManyToMany
@JoinTable(name="SPEAKER_PRESENTATION",
joinColumns=@JoinColumn(name="presentation_id"),
inverseJoinColumns=@JoinColumn(name="speaker_id")
)
private Set <Speaker> speakers = new HashSet <Speaker> ();

/**
* Default constructor
*/
public Presentation() {
}

public Long getId() {
return id;
}

public void setId (Long id) {
this.id = id;
}

public Timestamp getStartTime() {
return startTime;
}

public void setStartTime (Timestamp startTime) {
this.startTime = startTime;
}

public Timestamp getEndTime() {
return endTime;
}

public void setEndTime (Timestamp endTime) {
this.endTime = endTime;
}

public String getAbstractName() {
return abstractName;
}

public void setAbstractName (String abstractName) {
this.abstractName = abstractName;
}

public String getTitle() {
return title;
}

public void setTitle (String title) {
this.title = title;
}

public PresentationStatusEnum getStatus() {
return status;
}

public void setStatus (PresentationStatusEnum status) {
this.status = status;
}

public Timestamp getProposalTime() {
return proposalTime;
}

public void setProposalTime (Timestamp proposalTime) {
this.proposalTime = proposalTime;
}

public PresentationPlace getPresentationPlaceId () {
return presentationPlaceId;
}

public void setPresentationPlaceId (PresentationPlace presentationPlaceId) {
this.presentationPlaceId = presentationPlaceId;
}

public Set<Evaluation> getEvaluations() {
if (evaluations == null){
evaluations = new HashSet<Evaluation>();
}
return evaluations;
}

public void setEvaluations (Set<Evaluation> evaluations) {
this.evaluations = evaluations;
}

public void addEvaluations (Evaluation evaluation) {
getEvaluations().add(evaluation);
}

public Set<Speaker> getSpeakers() {
if (speakers == null){
speakers = new HashSet<Speaker>();
}
return speakers;
}

public void setSpeakers (Set<Speaker> speakers) {
this.speakers = speakers;
}

public void addSpeakers (Speaker speakers) {
getSpeakers().add(speakers);
}
}

Enumeration
You can note the 'equals (string s)' method which is quite handy.
package net.sf.mp.demo.conference.enumeration.conference;

import java.util.ArrayList;
import java.util.List;

public enum SponsorPrivilegeTypeEnum {

GOLDEN("Golden"),
SILVER("Silver"),
BRONZE("Bronze");

private final String value;

SponsorPrivilegeTypeEnum(String v) {
value = v;
}

public String value() {
return value;
}


public static SponsorPrivilegeTypeEnum fromValue(String v) {
for (SponsorPrivilegeTypeEnum c : SponsorPrivilegeTypeEnum.values()) {
if (c.value.equals(v)) {
return c;
}
}
return null;
}


/**
* Return a list that contains all the enumeration values
* @return List<SponsorPrivilegeTypeEnum> the that contains all the enumeration values
*/
public static List<SponsorPrivilegeTypeEnum> getList() {
List<SponsorPrivilegeTypeEnum> list = new ArrayList<SponsorPrivilegeTypeEnum>();
for (SponsorPrivilegeTypeEnum c : SponsorPrivilegeTypeEnum.values()) {
list.add(c);
}
return list;
}

/**
* @param value
* @return boolean : true if the value exist in the enum
*/
public static boolean contains (String value) {
for (SponsorPrivilegeTypeEnum c : SponsorPrivilegeTypeEnum.values()) {
if (c.toString().equals(value))
return true;
}
return false;
}

public static boolean containsValue (String value) {
for (SponsorPrivilegeTypeEnum c : SponsorPrivilegeTypeEnum.values()) {
if (c.value().equals(value))
return true;
}
return false;
}

public boolean equals (String s) {
if (s==null) return false;
return s.equals(value);
}

}
Java class as JPA2 entity for view
You can note the enrichment performed is translated into different Java and DB naming conventions for the entity itself as well as its dependencies.
Now you can use the view for all the JPA2 select operations.
package net.sf.mp.demo.conference.statistics;

import java.sql.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

import javax.persistence.*;

import net.sf.mp.demo.conference.statistics.MemberPerRoleCountryAndConference;

/**
*
* <p>Title: MemberPerCountryAndConference</p>
*
* <p>Description: Domain Object describing a MemberPerCountryAndConference entity</p>
*
*/
@Entity (name="MemberPerCountryAndConference")
@Table (name="stat_mb_per_ctry_conf")
public class MemberPerCountryAndConference {

@Id @Column(name="id" ,length=300)
private String id;

@Column(name="country", length=45, nullable=false, unique=false)
private String country;
@Column(name="conference_name", length=255, nullable=false, unique=false)
private String conferenceName;
@Column(name="number", nullable=false, unique=false)
private Long number;

@OneToMany (targetEntity=net.sf.mp.demo.conference.statistics.MemberPerRoleCountryAndConference.class, fetch=FetchType.LAZY, mappedBy="statMbPerCtryConfId", cascade=CascadeType.REMOVE)//, cascade=CascadeType.ALL)
private Set <MemberPerRoleCountryAndConference> memberPerRoleCountryAndConferences = new HashSet<MemberPerRoleCountryAndConference>();

/**
* Default constructor
*/
public MemberPerCountryAndConference() {
}

public String getId() {
return id;
}

public void setId (String id) {
this.id = id;
}

public String getCountry() {
return country;
}

public void setCountry (String country) {
this.country = country;
}

public String getConferenceName() {
return conferenceName;
}

public void setConferenceName (String conferenceName) {
this.conferenceName = conferenceName;
}

public Long getNumber() {
return number;
}

public void setNumber (Long number) {
this.number = number;
}

public Set<MemberPerRoleCountryAndConference> getMemberPerRoleCountryAndConferences() {
if (memberPerRoleCountryAndConferences == null){
memberPerRoleCountryAndConferences = new HashSet<MemberPerRoleCountryAndConference>();
}
return memberPerRoleCountryAndConferences;
}

public void setMemberPerRoleCountryAndConferences (Set<MemberPerRoleCountryAndConference> memberPerRoleCountryAndConferences) {
this.memberPerRoleCountryAndConferences = memberPerRoleCountryAndConferences;
}

public void addMemberPerRoleCountryAndConferences (MemberPerRoleCountryAndConference memberPerRoleCountryAndConference) {
getMemberPerRoleCountryAndConferences().add(memberPerRoleCountryAndConference);
}

}

Run
Since it generate a pom.xml, download maven and run 'mvn install'
It generates the lazuly backend jar containing the entities, the metamodel and the querydsl classes.