JPA Metamodel Generatie

In navolging van onder anderen Hibernate en Toplink is de Java Persistence API in versie 2.0 gekomen met een eigen criteria api. Dit artikel gaat in op de ondersteuning van typesafe queries door het gebruik van het metamodel. Ook wordt uitgelegd hoe de automatische generatie van dit metamodel te configureren met behulp van maven en hoe deze configuratie samenwerkt met een IDE als Eclipse.

Het maken van een query met behulp van deze API is eenvoudig:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> root = cq.from(Person.class);
cq.where( cb.equal(root.get("name"), "Jan") );

Bovenstaand voorbeeld compileert prima en op deze manier kan de criteria api ook worden gebruikt, maar op runtime kan de jvm erachter komen dat Person helemaal geen property ‘name’ heeft… Om dit probleem op te lossen is het metamodel ge├»ntroduceerd. Dit model beschrijft de datastructuur van het domeinmodel.
@Entity
public class Person {
private String name;
...
}

Met als metamodel:
@StaticMetamodel(Person.class)
public abstract class Person_ {
public static volatile SingularAttribute<Person, String> name;
...
}

Nu kan er een typesafe query worden gemaakt:
...
cq.where( cb.equal(root.get(Person_.name), "Jan") );

In plaats van de metamodel classes handmatig te schrijven, kunnen deze java sources ook worden gegenereerd. Dit kan bijvoorbeeld met behulp van Maven. De plugin die hiervoor gebruikt kan worden is de maven-processor-plugin. Er zijn verschillende metamodel generators te configureren, in onderstaand voorbeeld is gebruikt gemaakt van die van Hibernate: org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor.

Als eerste de dependency voor de model generator:
        <groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>1.1.1.Final</version>
<scope>provided</scope>

Scope staat op ‘provided’ omdat deze jar alleen gebruikt wordt tijdens de build.

Vervolgens wordt de compiler plugin zo geconfigureerd dat deze de annotatie processing stap overslaat en dat aan de maven-processor-plugin overlaat:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>

De maven-processor plugin is onder meer te vinden in de java-net repository:
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.0.0</version>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>process-sources</phase>
<configuration>
<processors>
<processor>
org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor
</processor>
</processors>
</configuration>
</execution>
</executions>
</plugin>

Voordat de compiler aan de slag gaat, zal de metamodel generator zoeken naar classes geannoteerd met @Entity, @MappedSuperclass en @Embeddable en hiervoor meta sources genereren. De directory waar deze gegenereerde sources worden neergezet (default: ./target/generated-sources/apt) wordt toegevoegd aan het compiler pad.

Nu dit werkt in Maven, is de logische vraag hoe bovenstaande in een IDE tot zijn recht komt. Er bestaan meerdere handleidingen hoe de annotatieprocessor het metamodel te laten genereren in verschillende IDE’s. Wanneer echter gebruik wordt gemaakt van maven verdient het de aanbeveling de IDE niet te gebruiken om de metamodel sources te maken.

Wanneer bijvoorbeeld gebruik wordt gemaakt van eclipse als IDE, en in de pom.xml de maven-eclipse-plugin is geconfigureerd, zal met het commando “mvn eclipse:eclipse” het metamodel al worden gegenereerd en de directory waarin dit wordt neergezet aan het .classpath bestand van eclipse worden toegevoegd als extra source locatie. Iets dergelijks is ook voor andere IDE’s met een Maven plugin te realiseren.

NB: De metamodel processor heeft soms wat moeite met het bepalen van het ‘accesstype’ (FIELD of PROPERTY) van subclasses uit een hierarchie die gemapped is met mapping-type ‘SINGLE-TABLE’. Hierdoor kan het voorkomen dat op verschillende platforms een verschillend metamodel de uitkomst is. Het verdient aanbeveling daarom het “accesstype’ op deze subclasses expliciet te maken, bijvoorbeeld met behulp van de @Access annotatie.

Referentie:

JPA modelgeneratie met hibernate