Welcome!

Java Authors: Dana Gardner, Miko Matsumura, John Ryan, Loraine Antrim, Walter H. Pinson, III

Related Topics: Java

Java: Article

Portable Persistence Using the EJB 3.0 Java Persistence API

The time for standardizing persistent POJOs has come

Persistence Operations
The way to operate on entities in JPA is by invoking a method on an entity manager and passing the entity as an argument to the method. The entity manager provides a common interface for entity operations and provides entity management within a transactional or even longer scoped context. For example, to persist a Flight entity, one only has to have access to an entity manager and invoke the persist() method on it, passing in the Flight entity as follows:

Timestamp depTime = Timestamp.valueOf("2006-09-30 05:07:0");
Flight newFlight = new Flight(552, depTime, "San Francisco");
em.persist(newFlight);.

When the transaction commits, the entity will be guaranteed to be committed to persistence storage. Likewise, one may use an entity manager to obtain a pre-existing instance of a Flight entity based on its flight id:

Flight flight552 = em.find(Flight.class, 552);

The entity manager has a complete and understandable API that is the main gateway to using JPA. However, persistence providers implement entity managers, and because allowances are made in the specification for different kinds of implementations, the semantics are sometimes a little looser than what you might expect. For example, in the persist() operation above we mentioned that the entity will be guaranteed to be committed to the database when the transaction commits. We didn't say when the data actually gets written to the database because the specification actually allows the provider either to eagerly write it or defer the write until the transaction commits. The only thing the user can rely on is that by the time the transaction has successfully committed the data will be in the database. It turns out that almost all vendors will actually defer the write because it's more efficient, but users who rely on this fact could be in trouble if they change providers (performance and scalability degradation aside).

The entity manager is a scaled-down version of the session API that has long been used in TopLink or a similar session API in Hibernate. The most common and useful operations on these session APIs have been normalized into the entity manager and represent an API that spans all persistence providers. Programming to the JPA EntityManager API will enable an application to be portable across these providers and prevent non-standard or proprietary features from slipping in.

Queries
To execute a query in JPA a query object must first be obtained from an entity manager. The query criteria is specified either dynamically in code or statically in metadata, and is normally expressed in terms of the JPA query language called the Java Persistence Query Language (JPQL). Queries are executed and depending upon the query the results may be returned either as entities, temporary non-entity objects, or even report data.

JPQL is based on EJB QL but is more powerful and more flexible. It still provides an abstraction language that's used to express queries in terms of entity state and relationships, but is expanded to include a host of new language features including a larger set of functions, outer joins, named parameters, sub-selects, aggregation, bulk updates and deletes, and much more. Because JPQL is a database-neutral language, queries expressed in JPQL are not only portable with respect to persistence providers but also across databases.

Queries may also be created using native SQL. This will typically reduce portability across databases but won't affect persistence provider portability. Queries that use SQL will produce uniform results and are mapped to entities in a standard way. SQL queries are discouraged, however, unless really needed, since they're less maintainable and result in a tighter coupling to the database.

A typical dynamic query to return a list of all of the flights going to a specific destination would be created and executed the following way. The destination is a named parameter that is bound to an argument before being executed, so the same query instance can be reused for querying different destinations. We'll find all the flights going to San Francisco.

Query q = em.createQuery(
    "SELECT f FROM Flight f WHERE f.dest=:destination");
q.setParameter("destination", "San Francisco");
List sfResults = q.getResultList();

While this query is completely portable in terms of its execution, what about the semantics of the query, or the results that are returned? Could the results differ depending upon the context in which it's executed? For example, if there was a transaction in progress and a new flight to San Francisco was added in the transaction, is that flight going to be returned by all providers? Remember that if the transaction hasn't been committed yet, depending upon the implementation, the new flight may not even have been written to the database.

The answer is that there is something called a flush mode that determines whether all changes in the transaction have been written out. By ensuring that the flush mode setting causes a flush to occur before transactional queries are executed, the results will always be the same regardless of the persistence implementation. In fact, to achieve portability and query results that most people would expect, the flush mode is set this way by default. To avoid the performance overhead of issuing a flush before a query, the flush mode is often changed. However, to ensure portability this should only be done when it's known that any entities modified earlier in the transaction won't affect the outcome of the query. Alternatively, queries can be executed outside of transactions, typically improving the performance of the query in the process.

One of the best practices for creating queries is to define them statically using the named query facility. Named queries are queries that are defined in annotations or XML and offer an additional form of application portability. The query criteria may be separated from the application code and filled in using JPQL, SQL, or any proprietary query language, such as the TopLink expression framework. The above query could be defined as a named query by defining the following annotation that specifies the name of the query and the query criteria as follows:

@NamedQuery(name="Flight.findByDestination", query="SELECT f FROM Flight f WHERE f.dest=:destination")

The query is invoked by obtaining an instance of the named query, binding the parameter, then executing it, similar to the dynamic query example above:

Query q = em.createQuery("Flight.findByDestination");
q.setParameter("destination", "San Francisco");
List sfResults = q.getResultList();

Vendor-specific Features
It's not unusual that a large application has requirements that the JPA 1.0 can't fulfill. After all, the first release of JPA included a lot of features, but as mentioned above, not everything was added. There are still a number of features up for discussion and possible inclusion in subsequent JPA releases that at this point in time are vendor-specific. Pessimistic locking is an example of a feature that's not usually required but on rare occasions is absolutely necessary.

Vendor-specific features can be accessed a number of different ways; some of them better than others. As we saw in the persistence unit metadata section, JPA does provide some mechanisms for vendors to incorporate additional features using standard APIs and the persistence properties are one such way. Query hints are also a way for additional query features to be accessed either programmatically or through metadata. Vendors can define their own query hints that users can add to named or dynamic queries before they're executed.

Additional XML files and annotations are another common way for users to add vendor-specific metadata. Proprietary annotations tend to be more dangerous than XML because they introduce compile-time dependencies in addition to any runtime dependencies that may exist.

In code the vendor-specific EntityManager implementation class can be retrieved by calling a special method on EntityManager and casting the result. A Query can also be cast to a proprietary interface or class. These practices should be used with care because they introduce code dependencies into the application.

Regardless of how the feature is used, the best way to organize vendor-specific feature use is to try to localize it to specific metadata and code areas. This way if porting is required it's easy to find the metadata or code that might need to change.

Conclusion
We've looked at only a few of the features of the EJB 3.0 Java Persistence API, but we have already seen how it can provide applications with a modern and portable platform for object-relational mapping and persistence. By combining the principal and most significant features of the major persistence solutions on the market and in the public domain, JPA can be used to write applications without being bound to any particular persistence provider or vendor. The underlying persistence implementation could be changed with few or no changes to the application code that uses it.

We did see a few examples of how some of the implementations may differ from each other though, so application developers should still be alert to the nuances of the provider implementation. A simple understanding of the API is all that's needed to develop simple persistence applications. However, to develop complex portable applications, a more thorough understanding is critical. For in-depth coverage on the features in JPA, as well as the portability issues of the API, we refer the reader to Pro EJB 3: Java Persistence API.

About Mike Keith

Mike Keith has more than 15 years of teaching, research and practical experience in object-oriented and distributed systems, specializing in object persistence. He was the co-specification lead for EJB 3.0 (JSR 220), a member of the Java EE 5 expert group (JSR 244) and co-authored the premier JPA reference book Pro EJB 3: Java Persistence API. Mike is currently a persistence architect for Oracle and a popular speaker at numerous conferences and events around the world.

About Merrick Schincariol

Merrick Schincariol is a senior engineer for the Oracle OC4J Java EE Container. He was a lead engineer for Oracle's EJB 3.0 release and co-author of Pro EJB 3: Java Persistence API. Before joining Oracle, Merrick developed enterprise and large-scale systems for the telecomunications industry.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.