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 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
>model-generation.cmd TRANXY-JPA2-2.xml
But what happened to my altered code?
It is kept. Your validation annotation are not erased!
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
- Integration by alteration/mutation
- 3 types of alteration
- artifact level (exclude for next generation)
- snippet level
- added part
- updatable part