In previous chapters, you’ve seen several different persistence mechanisms that either ship with Lift (Mapper) or have neat integration layers through Record (Squeryl and others). With this in mind, you may be wondering what the purpose of discussing yet another database access system is. Well, if you’re coming from the Java space and are familiar with JPA and the general Enterprise JavaBeans 3 (EJB3) setup, understanding how to work with Lift using this sort of system is important; particularly because many enterprise users already have existing investments in EJB-based systems.
Unlike the systems discussed in previous chapters, JPA makes fairly heavy use of annotations to drive various aspects of entity definition, and it takes a rather different approach to managing entity instances via its entity manager. We’ll be exploring this in more detail later, but here are some top-level reasons why you may be interested in using JPA:
■ JPA is fully interoperable with Java, so if you need Java code to call your entities, you can do so in a toll-free manner.
■ JPA is generally very mature and it’s a well-documented technology online. As such, advanced features such as second-level caching are well documented with examples.
■ Given a larger database schema, JPA can often be a more robust choice, because you can generally make more intelligent choices about how joins are imple- mented and what the relationships are between different entities.
In addition to JPA, JEE also encompasses the Java Transaction API (JTA), which pro- vides a general-purpose framework for constructing transactional semantics across multiple X/Open XA resources. There’s a Lift module for working with JTA, but it’s somewhat out of scope for this book.
13.2.1 JPA and Scala EntityManager
Lift’s JPA module depends upon a project called Scala JPA. This project provides an abstraction upon the EntityManager class provided by JPA in order to provide an API that’s much more comfortable for Scala programmers. Lift builds upon this work to provide some additional integration that’s helpful when building web applications that include JPA.
JPA and general Java persistence is a large topic that could easily fill entire books (such as Debu Panda, Reza Rahman, and Derek Lane’s EJB 3 in Action), so this section assumes you have a familiarity with the implementation of POJO entities. For readers who aren’t directly familiar with JPA, see table 13.2 for a brief introduction to some common terms.
In order to use JPA, you’ll need to specify the following dependencies in your project class. In this case, the EntityManager implementation is coming from the Hiber- nate project:
val jpa = "net.liftweb" %% "lift-jpa" % liftVersion val hibernate = "org.hibernate"
➥% "hibernate-entitymanager" % "3.6.0.Final"
val hibernatevali = "org.hibernate"
➥% "hibernate-validator-annotation-processor" % "4.1.0.Final"
These dependencies will allow you to define entity classes, complete with annotations, so that the JPA infrastructure can determine exactly what needs to happen, including validation. With your project now ready to use JPA, let’s build a small application that adopts the bookshop-style example used in both chapters 10 and 11 to demonstrate some of the lift-jpa features.
The following listing shows the implementation for two sample entities: Author and Book.
import java.util.Date import javax.persistence._
import javax.validation.constraints.Size
@Entity class Book {
Table 13.2 Overview of common terms used in and around JPA
Term Description
POJO This stands for plain old Java object and is a common term in discus- sions relating to JPA. You’ll be writing Scala, so the acronym doesn’t hold true here, but you can read this term to mean a plain class that doesn’t extend any specific class and that uses annotations to define field metadata.
EntityManager The official definition is: “An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. This interface defines the methods that are used to interact with the persistence context”.
This essentially means that if you want to do anything with JPA enti- ties in your application, you’ll require an EntityManager to interact with them.
Attached and detached objects
JPA entities have attached and detached modes. When an entity is attached, it’s available in the live JPA session, and any model alterations are persisted appropriately. A detached object can be interacted with in the same way, but the changes aren’t reflected in the database—you have to explicitly pull the entity back into the session via the EntityManager’s merge method.
Listing 13.11 Implementing the Author and Book JPA entities with Scala
Main entity annotation
B
311 Integrating Lift into existing Java infrastructure
@Id
@GeneratedValue(strategy = GenerationType.AUTO) var id : Long = _
@Size(min = 3, max = 60)
@Column(unique = true, nullable = false) var title : String = ""
@Temporal(TemporalType.DATE) @Column(nullable = true) var published : Date = new Date()
@ManyToOne(optional = false) var author : Author = _
}
@Entity
class Author { @Id
@GeneratedValue(strategy = GenerationType.AUTO)
var id : Long = _ @Size(min = 3, max = 20)
@Column(unique = true, nullable = false)
var name : String = ""
@OneToMany(mappedBy = "author", targetEntity = classOf[Book]) var books : java.util.Set[Book] = new java.util.HashSet[Book]() }
This listing defines the two entities this example will use. Notice how they’re regular Scala classes that are accompanied by the JPA annotations. These annotations should look pretty familiar if you’ve used JPA with Java before, but for the uninitiated, all enti- ties must first be annotated with @Entity B, and then each field variable D must be annotated with one of the many JPA annotations C, depending upon how it maps to the database.
Oh no, annotations!
Annotations have somewhat of a tarnished history with Scala, because many people really don’t like them and avoid their use entirely. In fact, until Scala 2.8 annota- tions simply did not work properly within Scala.
Annotations are commonly used in Java because the language itself lacks more sophisticated constructs that allow developers to write composable code. As such, annotations are a convenient way to build generic functionality in Java.
With Scala being a much richer language, annotations aren’t found in idiomatic Scala code, and their usage is usually reserved for Java interoperation only, as is the case with JPA. If you find yourself wanting to use annotations in your normal Scala code, you can likely factor it out into a set of composable functions or traits.
JPA field annotations
C
Field vars
D
Of particular interest in these annotations should be @Size. This is one of many validation annotations that Hibernate (or your EJB3 implementation of choice) provides. These validations will be automatically applied before persisting to the database, and specialized exceptions will be thrown if their conditions aren’t properly satisfied.
Now that you have the entities in place, you need to ensure that you define a persistence.xml file in src/main/resources/META-INF so that the JPA library knows how to interact with your database. Your persistence.xml should look like the follow- ing listing.
<?xml version="1.0"?>
<persistence>
<persistence-unit name="chp13"
transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.connection.driver_class"
value="org.h2.Driver"/>
<property name="hibernate.connection.url"
value="jdbc:h2:database/chp_13;FILE_LOCK=NO"/>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<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>
This file defines the connection parameters and gives JPA the information it needs to effectively determine how to construct queries using the right type of SQL dialect for your database. The first configuration point of interest is the name attribute on the
<persistence-unit> element B. This is the name you’ll refer to from your code, so use something meaningful.
Next the XML file defines the type of database and connection string JPA will use to establish that connection. This example is using the H2 database, so it specifies a path to the database file on disk and defines the SQL dialect that it should use when generating SQL queries. If your database requires security credentials, ensure these are populated in the hibernate.connection.username and hibernate.connection .password settings respectively C.
The second part of configuring JPA is to define an orm.xml file. Developers often argue that elements of entity definitions should not exist as inline code, but rather should be externally configured. This is essentially the role of orm.xml, and you can use it to define queries that each entity can execute and also to augment the definition
Listing 13.12 Configuring JPA with persistence.xml
Name of persistence unit
B
Database connection settings
C
313 Integrating Lift into existing Java infrastructure
that exists in the entity. This file must be created alongside persistence.xml in META- INF, and it should look like the following listing.
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings version="1.0"
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">
<package>sample.model</package>
<entity class="Book">
<named-query name="findBooksByAuthor">
<query><![CDATA[
from Book b where b.author.id = :id order by b.title ]]></query>
</named-query>
</entity>
<entity class="Author">
<named-query name="findAllAuthors">
<query><![CDATA[from Author a order by a.name]]></query>
</named-query>
</entity>
</entity-mappings>
You can see that this file is a descriptor of the entities in your application. You can define entities B and also the queries your application will make C. The JPA query syntax is quite extensive, so we encourage you to check the online reference: http://
download.oracle.com/javaee/5/tutorial/doc/bnbtl.html. Also understand that these queries are what are known as named queries—in the calling code you’ll look up the query by its identifying name, such as findBooksByAuthor.
As mentioned previously, it’s also possible to augment the entity information defined in the entity. For example, let’s imagine you wanted to specify a name for a given column. You could do something like this:
<entity name="Book">
...
<attribute-override name="title">
<column name="book_title" >
</attribute-override>
</entity>
This would then change the name of the column in the database to be “book_title” as opposed to “name”, which was defined in the entity code.
With this setup done, your application should now be able to talk to the database.
But note that the implementation is lazy, in that it will only attempt to connect upon the first call to the entity manager.
Listing 13.13 Augmenting entities with orm.xml
Entity package
B
Book entity and query
C
OBJECTS AND THE ENTITY MANAGER
Now that you have your entities and queries defined, you’d like to start interacting with them. For this you’ll require an entity manager, and Lift provides some neat abstractions for this atop of the default Java infrastructure so that the concept feels more native to Scala.
In order to create an entity manager, define an object that extends the lift-jpa type LocalEMF and extends RequestVarEM:
import org.scala_libs.jpa.LocalEMF import net.liftweb.jpa.RequestVarEM
object Model extends LocalEMF("chp13") with RequestVarEM
Scala objects are lazily created, so again this will only connect to the database when you touch it for the first time. Note that the LocalEMF class creates a connection from your application to the database, but if you’d prefer to look up a data source via JNDI, you can swap this class out for JndiEMF instead. Finally, the composed RequestVarEM is important because it allows you to define this singleton for accessing the underlying entity manager and keeps the JPA session live for each request, so you don’t have to do any additional plumbing. As the name implies, the RequstVarEM is underpinned by Lift’s RequestVar functionality, which keeps the entity manager request-scoped.
The entity manager typically has two modes for interacting with JPA entities:
attached and detached. The entity manager monitors attached entity instances until it’s instructed to flush these entities, when it will modify the underlying database with the appropriate changes. The practical advantage of object detachment in Lift is that you can obtain a reference to an entity object in the initial request cycle and make changes, and then easily reattach it to the live JPA session in the next request, com- plete with any changes you made. Objects can be explicitly attached to the JPA session via entity manager methods like merge and persist, or they can be implicitly attached using methods like find and getReference.
Now that you have everything in place to start making queries, let’s make a sim- ple listing and add an interface for adding Author entities to the database. The first thing you need to add is a new class called Authors. This class will contain two snip- pet methods that take care of the listing and adding of authors. The following list- ing shows the implementation.
import scala.xml.{NodeSeq,Text}
import scala.collection.JavaConversions._
import javax.validation.ConstraintViolationException import net.liftweb.common.{Failure,Empty,Full}
import net.liftweb.util.Helpers._
import net.liftweb.http.{RequestVar,SHtml,S}
import sample.model.{Book,Author,Model}
object Authors {
object authorVar extends RequestVar(new Author) }
Listing 13.14 Implementing the Author snippet
Define author RequestVar
B
315 Integrating Lift into existing Java infrastructure
class Authors { import Authors._
def author = authorVar.is def list =
"tr" #> Model.createNamedQuery[Author](
➥"findAllAuthors").getResultList.map { a =>
".name" #> a.name &
".books" #> SHtml.link("/jee/books/add", () => authorVar(a), Text("%s books (Add more)".format(
➥.books.size))) &
".edit" #> SHtml.link("add", () =>
authorVar(a), Text("Edit")) }
def add = {
val current = author
"type=hidden" #> SHtml.hidden(
() => authorVar(current)) &
"type=text" #> SHtml.text(author.name, author.name = _) &
"type=submit" #> SHtml.onSubmitUnit(() =>
tryo(Model.mergeAndFlush(author)) match { case Failure(msg,
➥ Full(err: ConstraintViolationException),_) =>
S.error(err.getConstraintViolations .toList.flatMap(c =>
<p>{c.getMessage}</p>)) case _ => S.redirectTo("index") })
} }
The Authors class contains two snippets, list and add, and it contains a RequestVar that’s shared for the class instance B. This RequestVar contains a new Author entity instance, so that even if the Add page is loaded, it will always have a reference to an Author that can subsequently be saved.
Looking first at the list snippet, you can see that it details using the Model entity manager to execute the predefined (named) query C from the orm.xml file cre- ated earlier in this section. It retrieves a list of all the authors in the database by using the Model.createNamedQuery method. Note the type parameter here, which tells the entity manager what the return type of this method will be; JPA is largely constructed of runtime operations, so it needs additional information about the types involved. After returning a list, the list snippet iterates through each item in the list, binding both a link to add books for this author and a link to edit this author’s previously saved information D. In both cases, note that the function bound to the link sets the current author value into the class RequestVar, author- Var. This way, when the page reloads with the Edit screen, for example, the author’s information is already prepopulated and the entity instance is attached to the live JPA session, ready for editing.
Get all authors
C
Bind links
D
Persist new instance
E
The add snippet should look like a fairly regular snippet method by now, but the main thing to make note of is how the submit function calls Model.mergeAnd- Flush(author)E. This is essentially telling the entity manager to take the changes already made to the model and update them in the database; this could result in either an update or insert operation in the underlying store.
This section has shown you how you can make use of Lift’s integration with the popular JPA libraries and infrastructure. Specifically, you’ve seen how to configure and get up and running with JPA while making use of Lift’s wrappers around the rather verbose JPA. This removes the Java-esque feel of its API and reduces what would typi- cally be a set of method calls and try/catch statements into simple one-line calls.