| Close Window |
The EJB 3.0 Java Persistence API (JPA) was released in May 2006 as part of the Java Enterprise Edition 5 (Java EE) platform, and it has already garnered a great deal of attention and praise. What began as merely an easier-to-use successor to the much-maligned container-managed persistence (CMP) portion of the EJB component standard soon evolved into a full-blown incorporation of the existing best practices of the most prominent and popular object-relational (O-R) persistence products in use. The result is that applications now have a modern standard for lightweight enterprise Java persistence that they can use in any compliant Java EE 5 application server, or in Java Standard Edition (SE) applications.
The Spring application framework has been in existence for four years, and it has become a popular choice both in an application server context and standalone. Like JPA, Spring is a technology designed to allow applications to be built from POJOs. The Spring Framework runs within whatever runtime context an application requires, and it supports applications by providing a wide range of services. Some of these services are abstractions over existing container-level services, whereas others add value to the Java EE container. The persistence access layer, which is particularly popular with the Spring community, is nicely integrated with whatever persistence runtime is being used and facilitates a sound testable architectural approach to working with persistent objects. Spring 1.x included support for a variety of open source and commercial persistence implementations such as TopLink, Hibernate, iBATIS, and JDO, as well as the standard Java database connectivity (JDBC) API that's part of the Java runtime. Spring 2.0 was a major milestone and introduced additional integrated support for JPA. In this article we'll discuss how to use Spring and JPA together and highlight some of the benefits that this architecture can bring to an application.
Spring as a JPA Container
The Java Persistence API was architected so it could be used both inside and outside a Java EE 5 container. When there's no container to manage JPA entity managers and transactions, the application must bear more of the management burden. When running in a JPA container the user experience is more hospitable.
One goal of the JPA specification was to make the technology pluggable. To enable this, the roles of container provider (the container or the side that has control of the runtime threads and transactions), and persistence provider (the provider or the part that implements the persistence API and manages the persistent entities) were defined, and a service provider interface (SPI) binds the two at deployment and runtime. A compliant JPA host container correctly implements this SPI from the container perspective. A compliant JPA persistence provider implements the SPI from the provider perspective. If both sides follow the rules, a compliant container should be able to run any compliant persistence provider implementation, and similarly, a provider should plug into any container.
Although Spring is neither an application server nor a Java EE 5 container, it does enhance, augment, and sometimes implement many of the services used in application servers. Spring 2.0 implements the container portion of the JPA SPI so it can be viewed as a JPA container. As such, it provides the class-loading and weaving support that JPA providers use to help manage the entities at runtime. Users benefit from an environment in which the runtime container and the JPA persistence provider are tightly integrated, but not necessarily in a Java EE 5 context. This provides many of the benefits of Java EE persistence without requiring a Java EE container.
Defining Entities
The most basic part of using JPA is to design and create the entities to be persisted. For the purposes of this article, we will use an extremely simplified library book inventory system with a single entity to illustrate the concepts concretely in Java code.
We'll create a simple Book entity by defining the class and annotating it with an @Entity annotation. The table that stores book instances will default to BOOK, which is exactly what we want. The primary key identifier is the isbn field, so we annotate that field with @Id. Because the title and author fields are basic mappings from the object fields to columns of the same name in the database table, we don't have to do anything to them. We want the genre field to map to a database column named CATEGORY, so we give it a @Column annotation. The resulting Book entity class is shown below.
package org.bookguru;
import javax.persistence.*;
@Entity
public class Book {
@Id private int isbn;
private String title;
private String author;
@Column(name="CATEGORY")
private Genre genre;
// Constructors, getters and setters, etc.
}
Of course a real application would have many entities, but because we want to focus on the use of JPA in Spring, we won't explain how to define and map JPA entities. For more information on defining JPA entities see Pro EJB 3: Java Persistence API.
Using JPA Entities in Spring
The primary way to operate on entities is by using an entity manager. The EntityManager API is the main gateway into JPA and supports basic create/read/update/delete (CRUD) operations. It acts as both a manager of all loaded entities and a factory for queries that enable more entities to be loaded. An entity manager is analogous to an Oracle TopLink session, Hibernate session or an equivalent interface provided by many O-R mapping frameworks.
For example, to create a new persistent entity we would simply create a new Java object of the correct entity type, invoke the persist() method on the entity manager, and pass the new entity as a parameter. Assuming we have access to an entity manager, the code to create a new book is simple.
Book book = new Book(12769356, "War and Peace", "Leo Tolstoy", Genre.FICTION);
entityManager.persist(book);
Using JPA and the entity manager in Spring is very simple. In most cases it's simply a matter of annotating a field or method of a Spring bean with @PersistenceContext, which causes an entity manager to be injected. Then invoke the entity manager in the context of a container transaction. Note that @PersistenceContext is a standard JPA annotation and not specific to Spring.
Using Spring, the transaction can be started and committed (or rolled back) at method entry and exit. All that needs to be done to achieve this is to declaratively state that the automatic transaction demarcation should happen. In Spring 2.0 the easiest way to do this is by annotating the bean class or method with the @Transactional annotation, although it's also possible to use XML metadata that doesn't require annotating Java code. The type of transaction that's started depends on the type of transaction manager that's configured in the Spring application context; knowledge of the underlying transaction infrastructure is completely abstracted from the Java code.
An example of a Spring bean that's transactional and uses an entity manager to perform JPA operations is the BookInventorySystem class shown below.
package org.bookguru;
import javax.persistence.*;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class BookInventorySystem {
@PersistenceContext(unitName="BIS")
EntityManager em;
public void addBook(int isbn, String title, String author, Genre genre) {
Book book = new Book(isbn, title, author, genre);
em.persist(book);
}
}
This class looks fairly ordinary except that the presence of two additional annotations, @Transactional and @PersistenceContext, provides us with a great deal more functionality. The @Transactional annotation causes all the methods in the class to get an automatic transaction, so a transaction will be provided whenever a caller invokes the addBook() method. We could just as easily have annotated the method directly to get this behavior, but the likelihood of adding more methods that also need a transaction is quite high, so the class is the best place for it.
The em field will get injected with an instance of EntityManager. The entity manager injected will be configured according to the named persistence unit referred to in the unitName attribute of the @PersistenceContext annotation. Named persistence units are defined and configured in the JPA persistence.xml configuration file and in the Spring application context as part of the entity manager factory bean (see Configuring the Application Context later in this article).
Despite the sparseness of the operations (we could fill it in with more operations and queries, but it's sufficient for purposes of illustration), we have a functional system, and we can now turn to the configuration.
Configuring persistence.xml
The standard JPA configuration file is an XML file called persistence.xml, and it's placed in the META-INF
directory of the jar archive or on the classpath. When using JPA in
most runtime environments, this file will contain most of the runtime
configuration information (except O-R mapping). However, when using JPA
in Spring, this file contains only the definition of the persistence
unit and sometimes a list of the entity classes (if not running in a
server deployment environment). An example of a persistence.xml file for us is shown below.
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="BIS" transaction-type="RESOURCE_LOCAL">
<class>org.bookguru.Book</class>
</persistence-unit>
</persistence>
The type of transaction also depends on the deployment environment. In this example, we're running in a simple Java SE virtual machine (VM) and don't have access to a JTA transaction manager, so we set the transaction type to RESOURCE_LOCAL.
Configuring the Application Context
Every Spring
application must eventually construct an application context - a set of
bean definitions that specify the dependencies that a bean has on
others. A Spring "bean" is a component in the application; it's
configured by Spring and eligible to benefit from the services Spring
provides. The application context determines how the beans fit together
at runtime and provides the flexibility to rewire parts of an
application in different ways without having to change the application
Java code.
Configuring the Entity Manager Factory Bean
As
part of its support for JPA, Spring 2.0 provides several JPA-related
classes intended to be used as Spring beans. The most important of
these is the entity manager factory bean, which makes a JPA entity
manager factory available to the application. This bean has
dependencies that determine the parameters of JPA execution in Spring,
and although many of these settings can be defined in the JPA persistence.xml
file, the Spring application context can provide additional flexibility
and configur-ability. It also provides a configuration style and
experience that's consistent with what Spring users are accustomed to.
Configuring the entity manager factory bean involves configuring three main dependencies: the persistence unit name, the data source, and the load time weaver. This is done as follows:
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="BIS"/>
<property name="dataSource" ref="dataSource"/>
<property name="loadTimeWeaver"
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
<property name="jpaVendorAdapter" ref="vendorAdapter"/>
</bean>
The type of component created by this bean definition is EntityManagerFactory, which is the starting point for JPA usage.
The persistence unit name is just the name of the persistence unit, and the data source is defined in the usual way. Spring always uses one or more Java 2 Standard Edition (J2SE) data source definitions as the starting point for persistence configuration. A number of data source types are available, but in this case we're using a simple pooled JDBC data source defined as follows:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@booksvr.org:1521:BOOKS"/>
<property name="userName" value="scott"/>
<property name="password" value="tiger"/>
</bean>
The loadTimeWeaver property specifies the weaving strategy that Spring uses to implement the container provider SPI and provides the weaving capability. In this example we use the instrumentation feature introduced in the Java SE 5 VM (specified on the jre command line) so we specify the InstrumentationLoadTimeWeaver class. If we were running in a Tomcat server, we would set this to ReflectiveLoadTimeWeaver and use the Tomcat class loader provided by Spring. If we were running Spring inside the Oracle Containers for J2EE (OC4J) server, we would set it to OC4JLoadTimeWeaver, which plugs into the special class-loading support in OC4J.
Configuring the Vendor Adapter
A keen observer
might have noticed that we snuck an additional fourth dependency into
the entity manager factory bean and wired it to a bean called vendorAdapter. The jpaVendorAdapter
property is an optional property that facilitates setting
vendor-specific properties that are common across providers. These
properties detail:
The following is the definition of the vendorAdapter bean that we wired to the jpaVendorAdapter property. It defines TopLink as the vendor and gives values to the three common property settings.
<bean id="vendorAdapter" class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
<property name="databasePlatform" value="${platform}"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
The databasePlatform string is understood by the persistence provider so even though the property name is common, the values may be different across vendors. We have assigned it the variable platform and defined it in an external application context properties file. (See Professional Java Development with the Spring Framework for a description of how to define and use properties files.).
Implementations such as TopLink define many more JPA settings that can be used to configure the provider in ways ranging from specifying what kind of cache to use to declaring custom classes and mapping types. These additional properties are typically defined as properties in the persistence.xml file.
Other Configurations
The next thing we need to do is declare the BookInventorySystem
class as a Spring bean. The simple bean definition merely points out
that Spring should proxy and manage instances of the class as they are
created.
<bean class="org.bookguru.BookInventorySystem"/>
Next, we'll use the local resource-level transactions provided by the JPA entity manager, so we define the transaction manager bean and bind it to the JpaTransactionManager class. We then refer its entity manager factory dependency to our entity manager factory bean.
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
This transaction manager is designed to support transactional connections through JPA but it will also allow direct JDBC access using Spring's JDBC abstraction library.
We need to do a bit of housekeeping to indicate to Spring that it should honor and act on any @PersistenceContext and @Transactional annotations found in bean classes. This is done by adding the following two simple elements:
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<tx:annotation-driven/>
The tx namespace and schema should be added to the top of the application context XML file so the namespace can be recognized.
Testing
Spring is designed to facilitate agile
development practices - particularly to make testing much easier than
in traditional Java EE development. The use of dependency injection and
POJO programming makes unit testing much easier in general, but Spring
doesn't stop there.
Spring provides a powerful integration test facility that lets code that accesses persistent data be tested without deploying to an application server or any container other than Spring. This functionality is packaged in the spring-mock.jar file included in the Spring distribution and provides the following services:
We extend Spring's AbstractJpaTests superclass, which is a subclass of JUnit TestCase, and then we specify the test fixture and configuration location. The test fixture will be automatically dependency-injected if we provide a setter method. We can provide any number of setter methods, but it's usually good practice to test one thing at a time. We must implement the getConfigLocations() method to return an array of the Spring configurations we want to load. Note that most of the configuration data is identical to that used in a deployed scenario, minimizing the amount of additional work needed to implement tests:
package org.bookguru;
import org.springframework.test.jpa.AbstractJpaTests;
public class BookInventorySystemTest extends AbstractJpaTests {
private BookInventorySystem bookInventorySystem;
public void setBookInventorySystem(
BookInventorySystem bookInventorySystem) {
this.bookInventorySystem = bookInventorySystem;
}
protected String[] getConfigLocations() {
return new String[] {"/my/path/my-spring-config.xml"};
}
}
Now we can add any number of test methods. These can access data by using our data access object (DAO) fixture or the jdbcTemplate and sharedEntityManager instance variables inherited from JpaTestCase as follows:
public void testAddBook() {
int oldBookCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM BOOK");
bookInventorySystem.addBook(12769356, "War and Peace", "Leo Tolstoy", Genre.FICTION);
sharedEntityManager.flush();
int newBookCount = jdbcTemplate.queryForInt("SELECT COUNT(0) FROM BOOK");
assertEquals("Must have added new row in BOOK table",oldBookCount + 1, newBookCount);
}
Here we're using JDBC queries in the same transaction to verify the correct behavior of our DAO. First we query for the number of rows in the BOOK table. Then we add a book, being sure to flush the current unit of work, using the EntityManager.flush() method. This forces the persistence provider to issue the necessary SQL update. Now we can issue another JDBC query to verify that we added an additional row. We know that our DAO doesn't merely execute without exception, but also causes the appropriate changes in our database. Those changes will be rolled back when the testAddBook() method has completed, so the changes won't be persisted or affect other tests.
With this approach, we can very quickly validate our O-R mappings and queries, as well as our Spring configuration. Very quick round trips mean that we can rapidly iterate as we enrich and map our domain model, identifying any problems early so that they take minimal time to rectify.
Best Practices
As we see from the example, most of
the effort to develop JPA in Spring is in the initial configuration
setup. Once we get the application context settled, the rest is just a
matter of simple programming as we add more classes and beans. When
working with multiple Spring/JPA projects it helps to have a template
XML file that can be copied and modified as needed. If a specific
architecture and pattern is commonly used, it may make sense to have a
JPA-specific bean definition file and just file-include it in the
application context file for every project. (Note that Spring allows a
configuration to be split into any number of XML files.)
In the past, each persistence implementation had a different session API, and using a Spring template/DAO was helpful because it let Spring manage session-level resources and insulate the program from the vendor API. JPA is a standard API, so there's no longer a need to do this kind of shielding. In Spring 2.0 the entity managers are managed for you, so although Spring supports the same kind of DAO templates for JPA, they're no longer as necessary.
Spring DAOs also provide exception translation by mapping the various platform and database exceptions into a consistent Spring exception hierarchy. This provides the application with a normalized, unified exception-handling scheme, regardless of the particular database sitting underneath. In Spring 2.0 this facility is made available through the @Repository annotation, without requiring the use of DAO/template objects. Because there's currently no standard way for database exceptions to be wrapped in a JPA PersistenceException, this annotation will help applications process persistence exceptions and map them to specific causes. It's also particularly valuable for existing Spring applications that will migrate from proprietary data access APIs to JPA, or for mixing JPA and JDBC use in the same application. As in any Spring application, using Spring with JPA ensures a consistent and testable programming model, whether you're deploying to a Java EE application server, a Web container such as Tomcat, or a standalone application.
Summary
In this article we've shown how to use JPA
in new or existing Spring applications to achieve standardized
persistence, with little effort or change in coding style. New Spring
users can begin writing applications and bring their JPA experience
with them. The flexibility and loose coupling that Spring offers, with
the standardization of JPA persistence, provides developers with the
best of both worlds and gives them a platform that's easy to develop on
and convenient to test.
The JPA Reference Implementation can be downloaded from http://otn.oracle.com/jpa, and Spring 2.0 can be downloaded from www.springframework.org/download. To learn more about using JPA with Spring, see the Spring JPA documentation at http://static.springframework.org/spring/docs/2.0.x/reference/orm.html#orm-jpa.
References
• Mike Keith and Merrick Schincariol. Pro EJB 3: Java Persistence API. Apress, 2006.
• Rob Harrob and Jan Machacek. Pro Spring. Apress, 2005.
• Rod Johnson, Juergen Hoeller, Alef Arendsen, Thomas Risberg, Colin Sampaleanu. Professional Java Development with the Spring Framework. Wrox, 2005.
• Rod Johnson, Juergen Hoeller, Alef Arendsen, Colin Sampaleanu, Rob
Harrop, Thomas Risberg, Darren Davison, Dmitriy Kopylenko, Mark
Pollack, Thierry Templier, Erwin Vervaet, Portia Tung, Ben Hale, Adrian
Colyer, John Lewis, Costin Leau, Rick Evans, Spring Framework 2.02 Reference Documentation. 2007.
© 2008 SYS-CON Media Inc.