From the IBM WebSphere Developer Technical Journal.
The Java Persistence API (JPA) defines several ways to access data: either through the entity manager, through JPA-QL, or through native queries. JPA makes use of annotations as a mechanism to map Java objects to the underlying database. You can also provide XML metadata as an overriding or alternative mechanism to the mapped annotations. However, most JPA usage I have seen out there has clearly favored the use of annotations. The fact that the specification documentation presents all of its examples using annotations and no examples of XML-based mapping samples (it only shows you an XML Schema) is likely one of the overriding reasons this is so. Object Relational Mapping was created to abstract details of the underlying database from the Java object model. However, JPA may be letting database details quickly creep back into the Java source. In this commentary, I will examine the various query styles in JPA, rationalize why they exist, and explain why certain styles (such as named queries) just make no sense to be annotated. I will conclude by showing that this small example is really part of a much larger problem.
Let me quickly go through the various ways of accessing data with JPA, assuming you are familiar with how to map Java objects in JPA. (For more thorough details, see Resources.)
Applications interact with Java objects at run time. Through the use of a special object called the entity manager, applications can query for, or persist, objects. An EntityManager instance is associated with a persistence context. Within the persistence context, the entity instances and their lifecycles are managed. You can think of the EntityManager as a facade to the underlying persistence mechanism. The EntityManager contains the necessary methods for accessing data. The simplest way to access persistent data is by using the find method. Here is an example of an application using the entity manager to find an object by its primary key:
Customer customer = (Customer)em.find(Customer.class,customerId); |
The find method requires you to know the primary key as well as the actual class type.
JPA also has a fully functional query language that can work against an object model. The JPA query language contains much functionality for more complex queries. Queries can be passed in dynamically to the entity manager:
Query q = em.createQuery("SELECT c FROM Customer c WHERE c.name LIKE
:custName");
q.setParameter("custName", name);
q.setMaxResults(10);
List result = q.getResultList(); |
The ability to pass in queries at run time is necessary for some dynamic cases, such as unknown conditions. However, in most cases, you want to lock down your queries based on thorough performance testing.
JPA also enables you to use native SQL queries against the underlying tables, and provides the ability to map back the results:
Query q = em.createNativeQuery( "SELECT o.id, o.quantity, o.item, i.id, i.name, i.description "+ "FROM Order o, Item i " + "WHERE (o.quantity > 25) AND (o.item = i.id)", "OrderItemResults"); |
There are many cases where standard SQL is necessary. I gave many reasons in a previous Comment lines column.
In most cases, you want to define well known queries that can be reused. Named queries enable you to define a query in a single location. There are many benefits to named queries:
- Externalizing or separating the query enables some flexibility in changing the query. This can be useful for fine tuning queries and locking them down.
- You can use the query from multiple locations.
- You can associate queries with well known business names.
You can define a named query as an annotation and execute it somewhere else in the code. Here is an example of using executing this type of named query:
Query allOrders = em.createNamedQuery("getAllOrder");
allOrders.setParameter("customerId",customerId);
Collection <CustomerOrder> results = allOrders.getResultList();
return results; |
Below is an example of how you can define a named query and associate it with an entity class:
@Entity
@Table(name="ORDER")
@NamedQuery(
name="getAllOrder",
queryString="SELECT OBJECT(o) FROM CustomerOrder as o WHERE
o.customerId = :customerId")
public class CustomerOrder implements Serializable {
|
Problems with annotated named queries
Wait a minute!! Didn't I just say that a named query is a great way to externalize queries? Although defining a named query as an annotation can make it more reusable in the code, the benefit is so minimal that it is essentially useless. I can easily wrap the named query in a method and execute it again and again. The major benefit is the externalization and, therefore, an annotated named query makes little sense, if any.
A NamedQuery can also be defined using XML metadata:
<named-query name="getAllOrders" queryString="SELECT OBJECT(o) FROM CustomerOrder as o WHERE o.customerId = :customerId" /> |
Now, I can easily make changes to named queries without changing my source code and recompiling. Thinking back to my CMP days, the fact that the query was in the XML deployment descriptor made this factor insignificant. All the queries were defined in a single ejb-jar.xml file that was packaged in an EJB JAR that was further packaged in the EAR. The amount of work to change the JPA-QL in the XML file was still significant. (Martin Fowler also makes this point.)
Although the idea of alternative descriptors was available, the monolithic metadata made it an unattractive option.
JPA has three import differences:
- Mapping files can be packaged in any JAR (or even packaged loosely) as long as they are available in the classpath.
- You can have as many mapping files as you want. You can design to have related entities mapped in one XML file, or you can separate them out. You can keep all your queries in one separate XML file. This is something I would recommend.
- Finally, you can have named native queries, and map the results back to a POJO.
For example, any queries you suspect can be changed can be placed externally. Consider the amount of wasted time spent if I had to change the source, perform a build, and install the application. If I could just change the query and restart, then I can test much quicker. Being able to change queries quickly during performance testing can drastically reduce test cycles.
Another example is when an ISV, selling software, needs to fine tune a query for a product they are selling to operate with a particular database vendor chosen by the customer. For example, changing the ordering of the where clause statement to improve performance helps in certain databases. Not being able to change queries could be a deal-breaker.
The ability to use native named queries is even more powerful, because you can map complex queries using native SQL back to a POJO, even if they are already mapped by something else. Look at this example:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns:orm="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 jpa.xsd ">
<named-native-query name=" OrderItemResults " query="SELECT o.id,
o.quantity, o.item, i.id, i.name, i.description FROM Order o, Item i WHERE
(o.quantity > 25) AND (o.item = i.id)" result-class=""
result-set-mapping="OrderItemResults" />
<sql-result-set-mapping name="">
<entity-result entity-class="com.acme.Order.class">
<!-- <field-result column="" name=""/>
<etc..> -->
</entity-result>
<entity-result entity-class="com.acme.Item.class">
<!-- <field-result column="" name=""/>
<etc..> -->
</entity-result>
</sql-result-set-mapping>
</entity-mapping> |
In this above code, even if the Order or Item class is mapped with an annotation, a separate XML file, or not at all (I would then have to map each field), I can still override it with this XML. With JPA native queries, I can use JPA as a JDBC framework, similar to IBatis, when I need the flexibility of SQL. (I will show examples of this in my blog and future articles.)
I have chosen to use named queries to illustrate a much larger problem. Object relational domain mapping (ORM) was created as an abstraction enabling applications to use object-oriented technology to address their domain. ORMs abstract out the database. Annotated mappings defeat the purpose of such an abstraction. Although such abstractions are not desired all the time, it is appropriate for many cases.
The JPA specification in itself is very attractive. JPA supports mapping your domain model using external XML mapping files. However, there is very little in the way of examples and documentation out there to show this. The specification committees are especially guilty, since they only provide annotated examples, implying that annotation is the preferred mechanism for JPA. However, there are valid reasons that suggest mapping files may be the preferred mechanism:
Client applications may share the source code, and therefore have intimate knowledge of the underlying database. Clients may even be tempted to perform SQL operations on their own.
What if I want to map objects to multiple databases? Although I can use XML to override annotations, if an object has a few mappings then I may not want to favor any. In an SOA environment, where I may be shipping objects across an ESB, each service may want to have a mapping to its own environment.
If my database changes -- even if I use XML to override the mappings -- my source code does not semantically map the underlying mapping. Anyone not aware of the XML override can make mistakes and assumptions. Information -- like the database schema name - changes all the time during a normal deployment process, and having this in the source would definitely hinder deployment.
Annotations do make things easier for a developer, but simplicity comes with trade-offs. I think the JPA writers of the world are generally doing the community a disservice by not documenting clear examples of using the XML style of mapping for JPA.
Named queries are just one example of how annotations can be overused. Annotations serve many useful purposes, but I fear there is much abuse of them in the ORM space. In my blog, I will spend sometime showing examples of using the XML mapping style of JPA.
Thanks to Keys Botzum and Tom Alcott for their comments.
-
EJB 3 specification
-
Examining the EJB 3.0 Simplified API specification
-
Tired of hand coding JDBC? Use iBatis as a data mapping framework instead
-
IBM WebSphere: Deployment and Advanced Configuration
-
The EJB Advocate: Is EJB 2.x dead yet?
Roland Barcia is a Certified IT Specialist for IBM Software Services for WebSphere in the New York/New Jersey Metro area. He is a co-author of IBM WebSphere: Deployment and Advanced Configuration. For more information on Roland, visit his Web site.
Comments (Undergoing maintenance)





