Help

The JPA 2 metamodel is the cornerstone of type-safe criteria queries in JPA 2. The generated classes allow you to refer to entity properties using static field references, instead of strings. (A metamodel class is the fully-qualified class name of the entity class it matches followed by an underscore (_)).

It sounds promising, but many people using Maven are getting tripped up trying to get the metamodel generated and compiled. The Hibernate JPA 2 Metamodel Generator guide offers a couple of solutions. I've figured out another, which seems more elegant.

Just to refresh your memory of the problem:

  1. Maven compiles the classes during the compile phase
  2. The Java 6 compiler allows annotation processors to hook into it
  3. Annotation processors are permitted to generate Java source files (which is the case with the JPA 2 metamodel)
  4. Maven does not execute a secondary compile step to compile Java source files generated by the annotation processor

I figured out that it's possible to use the Maven compiler plugin to run only the annotation processors during the generate-sources phase! This effectively becomes a code generation step. Then comes the only downside. If you can believe it, Maven does not have a built-in way to compile generated sources. So we have to add one more plugin (build-helper-maven-plugin) that simply adds an additional source folder (I really can't believe the compiler plugin doesn't offer this feature). During the compile phase, we can disable the annotation processors to speed up compilation and avoid generating the metamodel a second time.

Here's the configuration for your copy-paste pleasure. Add it to the <plugins> section of your POM.

<!-- Compiler plugin enforces Java 1.6 compatibility and controls execution of annotation processors -->
<plugin>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>2.3.1</version>
   <configuration>
      <source>1.6</source>
      <target>1.6</target>
      <compilerArgument>-proc:none</compilerArgument>
   </configuration>
   <executions>
      <execution>
         <id>run-annotation-processors-only</id>
         <phase>generate-sources</phase>
         <configuration>
            <compilerArgument>-proc:only</compilerArgument>
            <!-- If your app has multiple packages, use this include filter to
                 execute the processor only on the package containing your entities -->
            <!--
            <includes>
               <include>**/model/*.java</include>
            </includes>
            -->
         </configuration>
         <goals>
            <goal>compile</goal>
         </goals>
      </execution>
   </executions>  
</plugin>         
<!-- Build helper plugin adds the sources generated by the JPA 2 annotation processor to the compile path -->
<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>build-helper-maven-plugin</artifactId>
   <version>1.5</version>
   <executions>      
      <execution> 
         <phase>process-sources</phase>
         <configuration>
            <sources>
               <source>${project.build.directory}/generated-sources/annotations</source>
            </sources>
         </configuration>
         <goals>
            <goal>add-source</goal>
         </goals>
      </execution>
   </executions>
</plugin>

The metamodel source files get generated into the target/generated-sources/annotations directory.

Note that if you have references to the metamodel across Java packages, you'll need to filter the annotation processor to only run on the package containing the entity classes.

We'll be protoyping this approach in the 1.0.1.Beta1 release of the Weld archetypes, which should be out soon.

Bonus material: Eclipse configuration

While I'm at it, I might as well show you how I enabled the JPA 2 metamodel generation in Eclipse. (Max may correct me. He's the authority on Eclipse tooling, so listen to what he says).

Start by adding the following dependency to your POM:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-jpamodelgen</artifactId>
   <version>1.0.0.Final</version>
   <scope>provided</scope>
</dependency>

Then, populate the .factorypath file at the root of your project with the following contents:

<factorypath>
    <factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
    <factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/hibernate-jpamodelgen/1.0.0.Final/hibernate-jpamodelgen-1.0.0.Final.jar" enabled="true" runInBatchMode="false"/>
    <factorypathentry kind="VARJAR" id="M2_REPO/org/hibernate/javax/persistence/hibernate-jpa-2.0-api/1.0.0.Final/hibernate-jpa-2.0-api-1.0.0.Final.jar" enabled="true" runInBatchMode="false"/>
</factorypath>

Refresh the project in Eclipse. Now right click on the project and select:

Properties > Java Compiler > Annotation Processing

Enable project specific settings and enable annotation processing. Press OK and OK again when prompted to build the project. Now, Eclipse should also generate your JPA 2 metamodel.

Happy type-safe criteria querying!

14 comments:
 
19. Jul 2010, 00:06 CET | Link

Hi Dan,

basically the same approach can be taken when working with the Maven annotation plugin. The big advantage I see of using this plugin is that the processor can be configured as sole plugin dependency (example taken from the Hibernate Validator reference guide):

...
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <compilerArgument>-proc:none</compilerArgument>
    </configuration>
</plugin>
<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <version>1.3.4</version>
    <executions>
        <execution>
            <id>process</id>
            <goals>
                <goal>process</goal>
            </goals>
            <phase>process-sources</phase>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator-annotation-processor</artifactId>
            <version>4.1.0.Final</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</plugin>
...

That way the processor is not part of the actual project classpath and you can't accidentally import stuff from there.

One more note on using JSR 269 APs within Eclipse: It's essential to set the project's Compiler compliance level to 1.6. If you don't do so, you can activate annotation processing in the preferences but the AP won't run without any notice. I've forgotten that more than once and wondered why nothing happened :-)

Later, Gunnar

ReplyQuote
 
19. Jul 2010, 16:56 CET | Link
Jesse

I tried this method, and the metamodel classes weren't compiled for me in the end. If I use this configuration, however, the metamodel classes do get compiled.

 
20. Jul 2010, 01:58 CET | Link
Pierce Wetter | pierce(AT)paceap.com

That doesn't work for me. I think you're missing something, because when the source is generated, its going into target/classes, not target/generated-sources/annotations. There must be some config line you had to set that? Either that or you had some classes left over.

 
21. Jul 2010, 17:09 CET | Link

without any proc options the javac (6 and above) will (a) compile existing java sources, (b) generate any needed java sources by calling discovered annotation processors and (c) compiling those generated java sources. In fact, I believe (b) and (c) are processed recursively until (b) produces no additional sources.

The issue with Maven + annotation processors is that by default javac puts the generated classes into the compilation output directory (aka the same place it puts the class files). If you are ok with that, using just the Maven compile plugin should suffice I believe. If thats not ok, thats where Maven starts making you jump through hoops.

As a counterpoint, here is how easy this is in my Gradle PoC The only difference is the ability to pass the -s <outputdir> to tell javac where to put any generated sources.

 
11. Aug 2010, 20:20 CET | Link
Pascal Thivent

I use the following setup:

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <source>1.6</source>
    <target>1.6</target>
    <compilerArgument>-proc:none</compilerArgument>
  </configuration>
</plugin>
<plugin>
  <groupId>org.bsc.maven</groupId>
  <artifactId>maven-processor-plugin</artifactId>
  <version>1.3.1</version>
  <executions>
    <execution>
      <id>process</id>
      <phase>generate-sources</phase><!-- this is actually the default, could be omitted -->
      <goals>
        <goal>process</goal>
      </goals>
      <configuration>
        <outputDirectory>${project.build.directory}/generated-sources/metamodel</outputDirectory>
      </configuration>
    </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-jpamodelgen</artifactId>
      <version>1.0.0.Final</version>
    </dependency>
  </dependencies>
</plugin>

And the directory containing the generated sourcecode is added to the list of source directories by the annotation processor plugin, as it should.

 
09. Sep 2010, 10:40 CET | Link

Dan's approach works provided that you add the hibernate-jpamodelgen dependency as a project dependency. However, this also means that the processor becomes a transitive dependency to the consumers of your project. This is probably not what you want. Using test scope does not work either, because your model classes live in the main source tree. Unfortunately, the maven-compiler-plugin refuses to use a plugin defined dependency as shown by the maven-processor-plugin. It also does not work defining hibernate-jpamodelgen as extensions and trying to get the compiler plugin to use it this way :( Right now the best way seems to be the maven-processor-plugin. Unfortunately, this means at the moment you need to add two more repositories - http://download.java.net/maven/2 and http://repo.jfrog.org/artifactory/plugins-releases.

 
08. Oct 2010, 19:01 CET | Link
The issue with Maven and annotation processors is that by default javac puts the generated classes into the compilation output directory

Recent versions of maven-compiler-plugin (2.2 or later; see MCOMPILER-75) automatically pass -s target/generated-sources/annotations so this should not be an issue; source-generating processors should just work with no configuration or additional plugins, so long as the processor is registered in the default way in some JAR in the compile dependency list.

 
09. Jun 2014, 16:39 CET | Link
jack

As a Newbie, I am permanently exploring online for articles that can be of assistance to me. Thank you digitale option

 
26. Jul 2014, 05:53 CET | Link
Rocky

You actually make it look so easy with your performance but I find this matter to be actually something which I think I would never comprehend. venus factor

 
31. Jul 2014, 02:50 CET | Link
jack

Thanks a lot for providing individuals with a spectacular seo fort lauderdale possibility, Im thoroughly enjoying the smart way you write articles in this blog

 
29. Sep 2014, 05:57 CET | Link
quan nguyen | nhquan85(AT)gmail.com

As a Newbie, I am permanently exploring online for articles that can be of assistance to me ve may bay tu sai gon di ha noi, ve24h.vn

 
13. Nov 2014, 08:40 CET | Link
click to investigate

More good care is given to use techniques that do really eliminate the primary historical value of items by changing them to the factor of irreversibility. click to investigate

 
14. Nov 2014, 14:09 CET | Link
celebrations

Since then, Sal the Salmon has been on the road in Oregon and Washington, appearing at farm celebrations and other events. Look for Sal at a sustainable farming event near you. click here to investigate

 
21. Nov 2014, 07:41 CET | Link

, I contacted my managers at Red Hat and recommended that we hire Lincoln. I argued that we don't just want this guy to join our community, we want him to be a leader in our community. We certainly didn't want him to be stuck behind a desk fixing form fields any longer.

Post Comment