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.