Lift and JPA (javax.persistence)
From Lift
Contents |
Introduction
Sometimes when you need to work with databases in lift the built-in persistence layer isn't a good fit. While Lift's persistence layer is quick and simple, when you get into more complex schemas, particularly with large numbers of tables or complex relationships between tables, it can become difficult to make things work cleanly. Fortunately, Lift has the benefit of access to the full ecosystem of Java persistence layers. In this particular example we'll be looking at the Java Persistence API, or JPA. JPA is an API that provides a simple, annotation (or XML) driven mapping of java objects to databases. Although we will touch briefly on JPA in this document, this tutorial isn't intended to be a tutorial on JPA; if you want that there are plenty of good JPA tutorials to be found on the web.
Thanks
I'd like to thank everyone who has helped refine the concepts and presentation here. In particular, David P. and Viktor Klang for helping with the EM-per-session issues. Martin Ellis pointed out some improvements to the Maven config. Charles Munat provided a lot of feedback to make the whole document more clear. Igor Khlystov pointed out the correct way to specify annotation elements in Scala.
Getting started (maven setup)
Our first step is to setup our Maven hierarchy. One factor in deciding how to do this is that the way JPA operates, the libraries typically want all of the entity objects to be in their own jar file along with their configuration. In order to do this, we'll set up a master project that contains separate modules, or sub-projects, for the JPA library and webapp. Maven will package each module separately and we can configure the pom.xml (Maven's project configuration file) in the webapp module to depend on the persistence module. First, we create our master project by creating a "root" directory. Next, we add the pom.xml for the master project in the project root. You can copy and paste the following text into a file called "pom.xml" in the master project root directory:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.foo.jpaweb</groupId>
<artifactId>master</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>JPA tutorial master project</description>
<packaging>pom</packaging>
<modules>
<module>webapp</module>
<module>persistence</module>
</modules>
</project>
The first four lines of this pom are pretty standard for any Maven project. There's a good explanation of the elements in a pom.xml file here: http://maven.apache.org/guides/getting-started/index.html#How_do_I_make_my_first_Maven_project. As you'll see in these examples, I'll be using "com.foo.jpaweb" as my groupId for all projects. The artifactId of the modules that I'm going to create match the names I defined above in the master project pom.xml file above. For the master project I'm using the artifactId "master", although you could name it anything that doesn't conflict (groupId + artifactId have to be unique). The packaging type "pom" tells Maven that this is a master project that contains subprojects. Right after that we define the actual subproject modules. By doing that, we can run Maven commands (compile, package, etc) at the master project root and it will recursively perform those commands on the modules. Now we create the persistence module (sub-project) using Maven's archetype:create command. An archetype is essentially a project template, and they make project setup in Maven very easy. Run the following command in your master project root directory:
mvn archetype:create -N -DgroupId=com.foo.jpaweb -DartifactId=persistence
This will create a subdirectory called "persistence" along with quite a few files and directories under it. Change directories to the new "persistence" subdirectory and edit the pom.xml there. Our main goal is to add in the JPA dependencies so that when we compile and package we have the correct jar files in our classpath.. In this example I'll be using Hibernate EntityManager as the JPA provider and HSQLDB for the database; edit to match your requirements. Toplink (another popular JPA provider) is also available in the maven repos, although I have no experience with it. As a side note, http://www.mvnrepository.com is a great place to find the available dependencies to put into your pom.xml files. In this example, we don't modify the scope of the dependencies so that the jar files are automatically added to our webapp war file. If you want to deploy the webapp into a JEE container that provides JPA, you can make it so that the JPA dependencies are only used for compilation and testing by adding a "<scope>provided</scope>" element in the dependency elements. You also have to tell Maven to use Java 5 source compatibility so that annotations work (default is 1.3). We do this by adding in a "build" element that controls the configuration of the Java compiler plugin. The final pom.xml for the persistence module should look like:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.foo.jpaweb</groupId>
<artifactId>persistence</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>persistence</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>ejb</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.3.1.ga</version>
</dependency>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
You might be asking why we're doing the persistence library in Java and not Scala. The simple answer is that currently the Scala compiler doesn't support java annotations properly, which can be used quite a lot in JPA. A few examples would be if you want to define multiple named queries attached to an Entity, or if you want control over how entities are joined together in a one-to-many or many-to-many relationship. Until the compiler is enhanced (the ticket is https://lampsvn.epfl.ch/trac/scala/ticket/294), I'm going to focus on Java for this tutorial since you should be able to do everything you want. If you'd really like to use Scala (and it does make things a lot simpler), please see #JPA in Scala. Whether you use Scala or Java, the persistent entities should go in the "persistence" module's source tree. Like I mentioned before, JPA providers (Hibernate, Toplink, etc) typically like to see the entities packaged in their own jar file. You *could* place your entities in the lift "com/foo/jpaweb/model" directory in the webapp source tree instead, but then the configuration and bootstrapping of the persistence provider becomes a lot more complex. As simple as Maven makes it to package it up in a subproject I really don't think you gain anything by going that route.
Now that we have the persistence module set up, we change back to the master project root directory and use the lift maven archetype:create command to create the webapp:
mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create -N \ -DarchetypeGroupId=net.liftweb \ -DarchetypeArtifactId=lift-archetype-basic \ -DarchetypeVersion=0.7.1 \ -DremoteRepositories=http://scala-tools.org/repo-releases \ -DgroupId=com.foo.jpaweb \ -DartifactId=webapp
This archetype has been provided by the Lift folks and makes setup very simple. In particular, the archetypeVersion parameter controls which version of Lift you'll be using. Genereally the only other thing you would want to change for that command line would be the groupId and artifactId. This should create a directory called "webapp" under your master project root. Again, we need to edit the pom.xml for the webapp module, so change to the "webapp" directory and use your favorite editor on the pom.xml. Most of the pom.xml should already be set up by the archetype; the main change we need to make is to add in a dependency on our "sibling" persistence module. Copy and paste the following into the "dependencies" element of the webapp pom.xml file:
<dependency>
<groupId>com.foo.jpaweb</groupId>
<artifactId>persistence</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
Finally, we need to clean out some of the cruft that the maven archetypes create for us. In particular, our persistence module is going to have an App.java file in the src/main/java tree and an AppTest.java file in its src/test/java tree, and the webapp module is going to have User.scala, HelloWorld.scala, AppTest.scala and Boot.scala in it. Delete everything except for the Boot.scala (we'll modify it later). The Boot.scala file defines the bootstrap.liftweb.Boot class, which Lift uses for startup configuration. At this point we have two modules ready for editing. The whole project structure should look like:
jpademo/
|-- persistence
| |-- pom.xml
| `-- src
| |-- main
| | `-- java
| `-- test
| `-- java
|-- pom.xml
`-- webapp
|-- pom.xml
`-- src
|-- main
| |-- resources
| |-- scala
| | |-- bootstrap
| | | `-- liftweb
| | | `-- Boot.scala
| | `-- com
| | `-- foo
| | `-- jpaweb
| | |-- comet
| | |-- model
| | |-- snippet
| | `-- view
| `-- webapp
| |-- WEB-INF
| | `-- web.xml
| |-- index.html
| `-- templates-hidden
| `-- default.html
`-- test
|-- resources
`-- scala
|-- LiftConsole.scala
`-- RunWebApp.scala
JPA Objects and config
Now let's take a look at what we want to do in JPA. For the sake of this tutorial we'll choose a simple example: a database of books and authors. We need two persistence entities, a Book and and Author, and we need to set up a persistence.xml config file. First, we'll edit the Book object. We'll go for something fairly straightforward: a title, published date and author. The Java class should look like:
package com.foo.jpaweb.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
This class represents a book that we might want to read.
*/
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
@Temporal(TemporalType.DATE)
private Date published;
@ManyToOne
private Author author;
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public Date getPublished() {
return published;
}
public Author getAuthor() {
return author;
}
public void setId(Long id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setPublished(Date published) {
this.published = published;
}
public void setAuthor(Author author) {
this.author = author;
}
}
As you can see, one of the nice things about JPA is that it requires very little modification to what would otherwise be a normal Bean-style object. First, we import a few of the required classes and annotations. Next, I've added in a few annotations to direct the mapping:
- @Entity
- a required annotation for any persistent entity. The JPA provider will scan class files for this annotation and automatically add any marked classes it finds
- @Id
- defines the "primary key" field for the entity
- @GeneratedValue(strategy = GenerationType.AUTO)
- Tells the JPA provider that it's responsible for generating new primary key values for new entities. The strategy element of type AUTO tells the JPA to choose an appropriate strategy based on the underlying database. For instance, MySQL has auto-increment fields while PostgreSQL has distinct sequence generators.
- @Temporal(TemporalType.DATE)
- Tells the JPA provider that even though we've defined a field of type java.util.Date (which holds data accurate to the millisecond), we only want the Year/month/day.
- @ManyToOne
- This tells the JPA provider that there will be many Books for a given Author. Usually this will mean that the underlying schema for the Book table will contain a join column with a foreign key relationship to the Author's primary key field.
There's a lot more I could do with the annotations but the main point here is Lift integration. You'll also notice that there isn't an annotation on the title field. JPA will automatically map any field in the class unless we mark the field with a @Transient annotation. For more details on the annotations, here are a few references:
- The actual spec reference: http://jcp.org/aboutJava/communityprocess/final/jsr220/index.html . You can click on the download link to get the PDF version of the spec, which goes into a *lot* of good detail.
- TopLink's JPA reference: http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-annotations.html
- Hibernate's JPA reference: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html
Next, we need to write the Author class:
package com.foo.jpaweb.model;
import java.util.Set;
import java.util.HashSet;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
/**
An author is someone who writes books.
*/
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private Set<Book> books = new HashSet<Book>();
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Set<Book> getBooks() {
return books;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
As you can see, very similar. The major difference is the inverse mapping (@OneToMany) on books that ties the author to the books. The "mappedBy" property on the annotation tells the JPA provider that the Book class's "author" property defines the join column. I've also added a cascade in so that if we remove an author or add a new book to the author then the Book table is properly updated. The next step is to set up the configuration file. JPA expects the config file, persistence.xml, to be located in the META-INF directory of the jar. In order to do this using maven, we need to add a new directory under the src/main folder called "resources". Anything in the resources folder is packaged directly into the jar file as-is, so we need to create a META-INF folder under resources and put our persistence.xml file there. For our purposes we'll be using the following config:
<persistence>
<persistence-unit name="jpaweb" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:file:jpaweb"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>
It basically sets up a connection to HSQLDB on a flat file. The "hibernate.connection" properties should be self-explanatory, but here are the others:
- hibernate.max_fetch_depth - Controls how deep hibernate can use outer joins in a single statement
- hibernate.show_sql - When set to "true", hibernate will print out the SQL it's using every time it does a database operation
- hibernate.hbm2ddl.auto - Controls whether hibernate will automatically generate the database schema on startup. "Update" says that if the database has no tables, or if the table definitions don't match what Hibernate expects, to either create or alter the tables.
There are many more properties that you can use explained here: http://www.hibernate.org/hib_docs/reference/en/html/session-configuration.html#configuration-optional
To keep things simple we'll be using local transactions (the "transaction-type" attribute on the persistence-unit element). With everything set for the persistence layer, we can package and install the jar for use by our web app by running "mvn install" in the persistence project root directory. This will compile the code, run tests (if we had any), package up the classes and persistence.xml into a jar file, and then install the jar file into our local maven repository.
Lift (finally!)
Now we get to the good stuff. The main thing we want to do within Lift is make it easy to use the JPA classes we've just put together. In order to do that, there are a few simple tricks.
Per-session Entity Manager
We want to make sure that the entity manager is available to our snippets/views/whatever for the duration of the request. In order to do that, we set up some magic in our Boot.scala as well as a helper object (thanks David P., Viktor and several others). First, let's look at the helper object. We're going to create an object in the com.foo.jpaweb.model package whose sole purpose is to hold a thread-local instance of the entity manager for our use throughout a given request:
package com.foo.jpaweb.model
import javax.persistence.{EntityManager,Persistence}
object Model {
val factory = Persistence.createEntityManagerFactory("jpaweb")
// Temporarily using ThreadLocal until we get lifecycle handling in RequestVar
val emVar = new ThreadLocal[EntityManager]
def em = emVar.get()
}
First, we set up a val that points to a singleton instance of our EntityManagerFactory. In an environment where JPA is provided (JEE, etc) this usually would be a JNDI lookup. After we have the factory we set up a ThreadLocal instance to hold the entity manager as well as a convenience accessor method. Now that we have that in place, we can do our Bootstrap magic:
package bootstrap.liftweb
import net.liftweb.util.LoanWrapper
import net.liftweb.http._
import Helpers._
import com.foo.jpaweb.model._
/**
* A class that's instantiated early and run. It allows the application
* to modify lift's environment
*/
class Boot {
def boot {
// where to search snippet
LiftRules.addToPackages("com.foo.jpaweb")
// Set up a LoanWrapper to automatically instantiate and tear down the EntityManager on a per-request basis
S.addAround(List(
new LoanWrapper {
def apply[T] (f : => T): T = {
val em = Model.factory.createEntityManager()
// Add EM into S scope
Model.emVar.set(em)
try {
f
} finally {
em.close()
}
}
}))
}
}
We start off pretty simple; set up our package search to the standard name. After that, delve into some less well-known aspects of Lift. S.addAround is a pretty cool method that allows you to implement the loan pattern (http://scala.sygneca.com/patterns/loan). In our case, it lets us intercept the request handling so that we can create and inject an entity manager into our threadlocal, and then allows us to close the EM when we return back up the call stack. The LoanWrapper trait provides the apply method that will be called on stack descent. Part of the reason that we take this particular approach is that RequestVar (which would be more appropriate for this IMHO) only supports the "setup" portion of the lifecycle; David P. said that he'd look into adding support for teardown in RequestVar. That would significantly simplify this and when it happens I'll rewrite this part of the tutorial.
A little help from my friends...
Generally, using JPA from Lift is pretty painless. Particularly with Scala 2.7.0 and above, the generics support makes it really easy to use JPA since most JPA methods use generics. There are a few cases, though, where it's nice to have some implicit defs in place to smooth out some wrinkles. The main place that this is an issue is with collections. JPA methods (and entities) have to use Java collections because the underlying ORM layer is going to use Java collections. In Scala, however, Java collections aren't as nice as real Scala collections. The scala.collection.jcl classes and traits provide some very nice wrappers, but they're not automatic. I've tossed together the following implicit defs, which I add to Model.scala:
import scala.collection.jcl.{BufferWrapper,SetWrapper}
...
implicit def setToWrapper[A](set : java.util.Set[A]) = new SetWrapper[A]{override def underlying = set}
implicit def listToWrapper[A](list : java.util.List[A]) = new BufferWrapper[A]{override def underlying = list}
In our particular example, that means that when you access Author.getBooks() you can use it just like a Scala set and the implicit defs will do the conversion for you. Just remember to import Model.{setToWrapper,listToWrapper} so that the implicit defs are in scope.
Author page(s)
Now we get to the fun part. I'm going to assume you know about how Lift uses templates, binding, etc, and jump right into the actual functionality. First, I create an AuthorOps class that will hold all of my snippet methods. Let's start out with a method to view the authors. I create a template file under src/main/webapp/authors/list.html that looks like:
<lift:surround with="default" at="content">
<head><title>Author Listing</title></head>
<table>
<tr>
<th>Name</th>
<th>Books published</th>
</tr>
<lift:AuthorOps.list>
<tr>
<td><author:name /></td>
<td><author:count /></td>
</tr>
</lift:AuthorOps.list>
</table>
</lift:surround>
Now I need a method in AuthorOps to bind the template:
def list (xhtml : NodeSeq) : NodeSeq = {
val authors = Model.em.createQuery("from Author").getResultList().asInstanceOf[java.util.List[Author]]
authors.flatMap(author =>
bind("auth", xhtml,
"name" --> Text(author.getName()),
"count" --> Text(author.getBooks().size().toString)))
}
The first line is really all we need to use JPA. We simply use our convenience method to pull the em out of the ThreadLocal (for our request), create a query and then execute it. getResultList returns a non-generic java.util.List, so we cast it to the correct generic equivalent. After that, our implicit def allows us to directly use flatMap as if it were a Scala List and then we use bind the results back into the template.
JPA in Scala
There are two major differences if you want to create JPA entities using Scala:
- You need to edit the persistence module's pom.xml to include:
- The Scala tools repository (Scala tools is what provides the Maven Scala builder plugin)
- The Scala library dependency
- The Scala tools dependency
- You need to write the entities in Scala :)
Let's start by looking at the modifications to the pom.xml file. Change to the persistence project's root directory and edit the pom.xml. First we add the Scala tools repository by copying and pasting the following into the pom.xml file as a child of the "project" element:
<properties>
<scala.version>2.7.0</scala.version>
</properties>
<repositories>
<repository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>scala-tools.org</id>
<name>Scala-Tools Maven2 Repository</name>
<url>http://scala-tools.org/repo-releases</url>
</pluginRepository>
</pluginRepositories>
The properties element actually defines the Scala version for later use by our plugins so that we only have to define it in one place. Now we need to add the dependencies by copying and pasting the following into the dependencies element of the pom.xml:
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
Finally, we add in the Scala build plugin for Maven at the end of the pom.xml under the project element:
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<scalaVersion>${scala.version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</build>
Now we use our favorite editor to write the Book.scala file:
package com.foo.jpaweb.model
import java.util.Date
import javax.persistence._
/**
This class represents a book that we might want to read.
*/
@Entity
class Book {
@Id
@GeneratedValue{val strategy = GenerationType.AUTO}
var id : Long = _
var title : String = _
@Temporal(TemporalType.DATE)
var published : Date = _
@ManyToOne
var author : Author = _
}
As you can see, the Scala version is much more concise than the Java version. We simply define the vars using the same annotations we would for the Java version. You'll notice that the annotations for GeneratedValue looks a little different. That's because in Scala the annotation is treated as a class, and the GeneratedValue annotation only has a default constructor. We have to override the "strategy" property as if it were a val. We also need to initialize each variable to a default value ("_") or the compiler will complain. Next, we need to write our Author class. Based on the Book.scala example above, we can translate the Java version to:
package com.foo.jpaweb.model
import javax.persistence._
/**
An author is someone who writes books.
*/
@Entity
class Author {
@Id
@GeneratedValue{val strategy = GenerationType.AUTO}
var id : Long = _
var name : String = _
@OneToMany{val mappedBy = "author", val targetEntity = classOf[Book]}
var books : java.util.Set[Book] = new java.util.HashSet[Book]()
}
Again, everything cleans up nicely. For some reason with Scala 2.7.1 the generics for the books set isn't being carried in the class, so we have to add the targetEntity value to the OneToMany annotation to tell the JPA provider what class is contained in the Set. Hopefully this will be resolved soon. I haven't "pushed the envelope" with annotations yet in JPA, so I'm not sure that all cases are covered. Worst case, you can create an orm.xml file and place it under your resources/META-INF directory (alongside the persistence.xml file). orm.xml allows you to override annotations on a per-property or per-entity basis. The full orm.xml DTD can be found here: http://www.jpox.org/xsd/jpa_orm_1_0.xsd. Read the section #Overriding Annotations with XML for more details.
Overriding Annotations with XML
While the annotations for JPA make it very easy to write entities, sometimes you need to be able to override the configuration at runtime. In the case of the current Scala compiler nested annotations aren't supported yet, making it impossible to do some things with annotations. In either case, there's a simple solution: use an orm.xml file to configure JPA behavior. The orm.xml file needs to be located in the META-INF folder of your persistent entity jar file. In order to do that, we'll create it in the src/main/resources/META-INF directory of your persistence project. As an example, let's add some named queries to our Book class. Named queries are a nice feature of JPA that allows you to define the query external to your code. They support named and positional parameters and the underlying provider uses prepared statements to execute them. Because of this they have a lot of the nice features of prepared statements, including improved security and efficiency in many cases. If we only had one query to associate with a particular entity, we could just use the @NamedQuery annotation and be done with it. If we have multiple queries, though, they have to be nested inside a @NamedQueries annotation and that doesn't work in Scala (yet). Instead, we'll define them in our orm.xml file. For a book let's start with a few simple queries: find books published between a start and end date, and find all books. The queries themselves would look like:
from Book b where book.published between :startDate and :endDate from Book
The complete orm.xml file looks like this (I'll explain below):
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0">
<package>com.foo.jpaweb.model</package>
<entity class="Book" metadata-complete="false">
<named-query name="findBookByDates">
<query><![CDATA[from Book b where b.published between :startDate and :endDate]]></query>
</named-query>
<named-query name="findAllBooks">
<query><![CDATA[from Book]]></query>
</named-query>
</entity>
</entity-mappings>
To start, every orm.xml file has to start with the same entity-mappings root element. After that, we have the following elements
- package
- Defines packages that unqualified classnames will be resolved relative to. In our case that means we can just call the Book class "Book" instead of "com.foo.jpaweb.model.Book". You can have multiple package elements if you have multiple packages with entities in them.
- entity
- Defines the entity mapping itself. We set the metadata-complete element to indicate that the mapping augments existing annotations. If you set it to true it will override all annotations on the entity. One thing to note: If you override a property annotation even with metadata-complete set to false, the mapping in the orm.xml file will completely replace any annotations on that property.
- named-query
- Should be self explanatory. The name attribute indicates the name of the query, and must be unique within your JPA unit (jar file, in this case).
- query
- The actual EJB-QL query. Note that I've tagged the queries as CDATA so that they're not parsed as XML. Otherwise, if you use XML entities (<, >, &, etc) you have to use the escaped versions (<, >, &, etc).
Now we have the mappings defined, but how do we use them? Simple, when we have a reference to an entity manager, we just use the createNamedQuery method:
val allBooks = em.createNamedQuery("findAllBooks").getResultList
val dateQuery = em.createNamedQuery("findBookByDates")
.setParameter("startDate", someStartDate)
.setParameter("endDate", someEndDate)
.getResultList

