Sun Java Solaris Communities My SDN Account Join SDN
 
Enterprise Java Technologies Tech Tips

The Java Persistence Query Language and EJB 3.0 Interceptors

 
 
In This Issue

Welcome to the Enterprise Java Technologies Tech Tips for October 28, 2006. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java Platform, Enterprise Edition (Java EE).

This issue covers:

These tips were developed using the Java EE 5 SDK. You can download the SDK from the Java EE Downloads page.

You can download the sample archive for the Java Persistence Query Language tip .

You can download the sample archive for the EJB 3.0 Interceptors tip.

Any use of this code and/or information below is subject to the license terms.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

The JAVA PERSISTENCE QUERY LANGUAGE

by Jie Lin Leng

One of the things that the Java Persistence API specifies is a query language that allows you to define queries over entities and their persistent state. The query language, called the Java Persistence Query Language, gives you a way to specify the semantics of queries in a portable way, independent of the particular database you're using in an enterprise environment.

This Tech Tip introduces the Java Persistence Query Language and discusses some of its basic features. The tip assumes that you understand the basic terms and concepts of the Java Persistence API. If you don't, see "Chapter 24: Introduction to the Java Persistence API" in the Java EE 5 Tutorial

A sample package accompanies the tip. The code examples in the tip are taken from the source code of the sample (which is included in the package). The sample uses the Java EE 5 SDK. You can download the Java EE 5 SDK from the Java EE Downloads page

A Simple Example

Let's start with a simple example that uses the Java Persistence Query Language. Suppose you have two entities, Customer and Order, which have a one-to-many relationship. Suppose too that the Customer entity class and the Order entity class are defined as follows:

   @Entity
   @Table(name="CUSTOMER_TABLE")
       public class Customer implements Serializable{

       public enum CustomerStatus 
         {FULL_TIME, PART_TIME, CONTRACT};
       @Id
       @Column(name="ID")
       private Integer customerId;
       @Column(name="CITY")
       private String city;
       @Column(name="NAME")
       private String name;
       @Enumerated(ORDINAL)
       @Column(name="STATUS")
       private CustomerStatus status;
       @OneToMany(mappedBy="customer")
       private Collection<Order> orders;

       ...
       }

   @Entity
   @Table(name="ORDER_TABLE")
      public class Order implements Serializable {
     
      @Id
      @Column(name="ID")
      private Integer orderId;
      @Column(name="QUANTITY")
      private int quantity;
      @Column(name="TOTALPRICE")
      private float totalPrice;
      @ManyToOne()
      @JoinColumn(name="CUST_ID")
      private Customer customer;

        ...
   }

Here's some client code that creates instances of the entities, creates an entity manager to manage their persistence, and inserts the instances in a database:

 
   // Create an EntityManagerFactory for a persistence unit 
   // called j2seEnvironment.
   EntityManagerFactory emf =
   Persistence.createEntityManagerFactory("j2seEnvironment");

   // Create an Entity Manager
   EntityManager em = emf.createEntityManager();
   
   // get a Transaction
   EntityTransaction tx = em.getTransaction();
   
   // create a POJO instance of the Customer class
   Customer customer = new Customer();
   customer.setCustomerId(new Integer(3));
   customer.setName("SUN_SALE");
   customer.setCity("SAN JOSE");
   customer.setStatus(Customer.CustomerStatus.FULL_TIME);
           
   // create a POJO instance of the Order class 
   // for this customer
   order order = new Order();
   order.setOrderId(new Integer(3));
   order.setQuantity(new Integer(2));
   order.setTotalPrice(new Float(22.30));
   order.setCustomer(customer);
           
   // Make the Customer and Order instances persistent 
   // and insert them into the database
   tx.begin();
   em.persist(customer);
   em.persist(order);
   tx.commit();

With persistence established for the entities, you can issue Java Persistence Query Language queries against those entities. For example, here is a simple query that you can add to the client code to list all the elements in the Customer entity instance whose name is "SUN_SALE" :

 // run a simple Java Persistence query language query
 String ejbql = "SELECT c FROM Customer c 
 WHERE c.name = 'SUN_SALE'";
 Query query = em.createQuery(ejbql);
 List result = query.getResultList();

Notice that the syntax of the SELECT, FROM, and WHERE clauses is similar to SQL. Also, notice that the query defines an identification variable (in this case, c) that represents the abstract schema of the Customer entity.

The CreateQuery method in the example is a factory method of the EntityManager for creating a Java Persistence query language query. It returns a Query object. The getResult method of the Query object executes the query and returns the query result as a List.

Named Queries

You can also predefine queries for an entity. These predefined, static queries are called named queries. A major advantage of using a named query is that you can rerun it multiple times, providing different parameters for each run.

To use a named query, you first define the named query using the @NamedQuery annotation. You do this in the pertinent entity class before the definition of the entity. For example, here's a named query defined in the Customer class:

   @NamedQuery (
       name="findCustomerByName",
       query="select c FROM Customer c WHERE c.name = :name"
   )
   })

   public class Customer {
   ...
   }

The named query, findCustomerbyname, retrieves all elements in the Customer entity instance whose name is provided by the named parameter :name. A named parameter in the Java Persistence Query language consists of a colon followed by an identifier.

Named queries can also be grouped together using the @NamedQueries annotation. For example:

   @NamedQueries({
   @NamedQuery (
       name="findCustomerByName",
       query="select c FROM Customer c WHERE c.name = :name"
   ),
   @NamedQuery(
       name="findCustomerbyOrderId",
       query="select c FROM Customer c JOIN c.orders o WHERE 
               o.orderId = :id"
   )
   })

   public class Customer {
   ...
   }

The second named query, findCustomerOrder, retrieves all elements in the Customer entity that have orders whose order identification is provided by the named parameter :orderId.

Query names are scoped to the persistence unit. In other words, they apply to the entity classes that are managed by the entity manager, in this case Customer and Order.

After you define a named query, you create it in the client code using the createNamedQuery method of the EntityManager. For example:

   List customers = em.createNamedQuery("findCustomerByName")
   .setParameter("name", "SUN_SALE")
   .getResultList();   

The setParameter method in findCustomerByName binds the name argument to the named parameter, :name , in the named query definition. The getResultList method returns the query result, which in this case are all elements in the Customer entity whose name is " SUN_SALE".

GROUP BY and HAVING Clauses

The Java Persistence Query Language also supports the GROUP BY and HAVING clauses. The GROUP BY clause allows you to aggregate values according to a set of properties. The HAVING clause allows you to further restrict the query result.

The GROUP BY and HAVING clauses have some special requirements. Any item that appears in the SELECT clause (other than as an argument to an aggregate function) must also appear in the GROUP BY clause. The conditional expression in a HAVING clause must be specified over the grouped items or over aggregate functions applied to grouped items.

Here are some examples that use the GROUP BY or HAVING clauses:
 
   // Group the orders by their customer and for each group 
   // return the customer and the average totalPrice
   String ejbql = "SELECT o.customer, AVG(o.totalPrice) 
   FROM Order o GROUP BY o.customer";
   Query query = em.createQuery(ejbql);

   // Group the customers by their city. For each group return 
   // the city and the number of customers in that group, but 
   // only if there are more than 3
   String ejbql = "SELECT c.city, COUNT(c.city) 
   FROM Customer c GROUP BY c.city HAVING COUNT(c.city) > 3";
   Query query = em.createQuery(ejbql);

   // Group the orders with a totalPrice over a certain limit 
   // per the name of their customer, but consider only 
   // customers whose name starts with 'SUN'). For each group 
   // return the customer name, the average, and maximum 
   // totalPrice.
   String ejbql = SELECT o.customer.name, AVG(o.totalPrice), 
   MAX(o.totalPrice) 
   FROM Order WHERE o.totalPrice > :limit 
   GROUP BY o.customer.name HAVING o.customer.name LIKE 'SUN%';
   Query query = em.createQuery(ejbql);

There's Much More

The Java Persistence Query Language is a full structured query language, and offers many more language features than those covered in this tip. These include language features for bulk update and delete operations, outer join operations, projection, and subqueries. For a more complete description of the language see "Chapter 27: The Java Persistence Query Language" in the Java EE 5 Tutorial.

A future tip will cover some common pitfalls in constructing Java Persistence Query Language queries.

Running the Sample Code

To install and run the sample code that accompanies this tip:

  1. If you haven't already done so, download Java EE 5 SDK from the Java EE Downloads Page , and install it.

  2. Set the following environment variables:
    • JAVAEE_HOME. This should point to where you installed the Java EE 5 SDK.
    • ANT_HOME.This should point to where ant is installed. Ant is included in the Java EE 5 SDK bundle that you downloaded. (In Windows, it's in the lib\ant subdirectory.)
    • JAVA_HOME. This should point to the location of JDK 5.0 on your system. JDK is included in the Java EE 5 SDK bundle that you downloaded. (In Windows, it's in the jdk subdirectory.)

    Add $JAVA_HOME/bin, $ANT_HOME/bin, and $JAVAEE_HOME/bin to your PATH environment variable

  3. Download the sample package and extract its contents. The JPQL directory below ttoct2006jpql contains the source files and other support files for the sample.

  4. Change to the JPQL directory and edit the build.xml file as appropriate. For example, set the value of javaee.home to where you installed the Java EE 5 SDK.

  5. Start the database server:
    $JAVAEE_HOME/bin/asadmin start-database
    In response you should see output similar to this:
          ...
               
          start-db:
               [exec] Database started in Network Server mode on 
               host ... and port ...
                
               [exec] Starting database in the background.  Log 
               redirected to ...
               [exec] Command start-database executed successfully.
    

  6. Build and run the sample application. From the JPQL directory enter the following command:

    ant all
    In response you should see output similar to this:
          ...
               [java] JPQL simple query SELECT c FROM Customer c WHE
          RE c.name = 'SUN_SALE returns Customers: [ejbql.models.Cus
          tomer@9cbd4b]
               [java] JPQL query with Named Query SELECT c FROM Cust
          omer c WHERE c.name = 'SUN_SALE' returns Customers: [ejbql
          .models.Customer@9cbd4b]
               [java] JPQL query with GROUP BY SELECT o.customer, AV
          G(o.totalPrice) FROM Order o GROUP BY o.customer returns [
          [Ljava.lang.Object;@c44b88, [Ljava.lang.Object;@13ad33d]
               [java] JPQL query with GROUP BY SELECT c.city, COUNT(
          c.city) FROM Customer c GROUP BY c.city HAVING COUNT(c.cit
          y) > 1 returns [[Ljava.lang.Object;@16921fd
               [java] JPQL query with GROUP BY SELECT o.customer.nam
          e, AVG(o.totalPrice), MAX(o.totalPrice) FROM Order o WHERE 
          o.totalPrice > :limit GROUP BY o.customer.name HAVING o.cu
          stomer.name LIKE 'SUN%' returns [[Ljava.lang.Object;@1acd4
          7, [Ljava.lang.Object;@19b04e2]
    

About the Author

Jie Lin Leng is a member of the Java Persistence Engineering group at Sun. She is currently involved in the development of the Java Persistence Query Language for EJB 3.0.

EJB 3.0 INTERCEPTORS

by Mahesh Kannan

One of the new features introduced in the EJB 3.0 specification is interceptors. An interceptor is a method that you can interpose in the invocation flow of an enterprise bean. You can define an interceptor to intercept an enterprise bean's business methods -- the interceptor method runs before any of the bean's business methods are invoked. Or you can define an interceptor to intercept lifecycle events for an enterprise bean -- the interceptor method runs as a callback method for the bean's lifecycle events.

In essence, interceptors give you a way to add functionality to your business methods without modifying the methods' code. For example, you can use an interceptor to validate parameters before they're passed to a business method, or perform security checks at the time the business method is called. Interceptors are also useful for actions such as logging and profiling that cut across multiple components in an application (these are sometimes called "crosscutting" operations). You also have the flexibility to chain interceptors together. Each interceptor in the chain can perform a specific operation before a business method is invoked or in response to a lifecycle event.

This Tech Tip demonstrates using interceptors to gather profiling information about business methods for an enterprise bean. A sample package accompanies the tip. The code examples in the tip are taken from the source code of the sample (which is included in the package). The sample uses the Java EE 5 SDK. You can download the Java EE 5 SDK from the the Java EE 5 SDK Downloads page.

An example bean

To demonstrate the interceptors, let's use a simple stateless session bean that provides a method to reverse a String. The bean implements a business interface called StringService.

Here is what StringService looks like:

   import javax.ejb.Remote;
   
   @Remote
   public interface StringServiceRemote {
      public String reverse(String str);
   }
And here is the StringServiceBean:
   import javax.ejb.Stateless
     
   public class StringServiceBean
      implements StringServiceRemote {
 
      public String reverse(String str) {
         //reverse the string
         ...
      }
   }

Defining an interceptor

As mentioned earlier, you can use interceptor methods to intercept either a business method or lifecycle event. An interceptor that intercepts a business method is typically called an AroundInvoke method because it can be defined by annotating the method with an @AroundInvoke annotation.

You can define an AroundInvoke method on the enterprise bean itself or on an external class (called an interceptor class in the rest of this tip). An interceptor class is just like any other class in the Java platform. It does not need to extend any special class or implement any interface.

Writing an AroundInvoke method

You can define an AroundInvoke method by simply annotating the method with an @AroundInvoke annotation (or you can define it through an element in the bean's deployment descriptor). An AroundInvoke method must satisfy the following requirements:

  • One AroundInvoke method is allowed for each class.
  • It must have a no arg public constructor.
  • It must take an javax.interceptor.InvocationContext object as an argument and return a java.lang.Object object.
  • It can throw any application exception that is specified in the business interface, or any runtime exception.
  • It can be declared private, package private, protected, or public.
  • It must call InvocationContext.proceed() to signal its intention to continue the invocation.

Here is an interceptor class that prints the time it takes to run an intercepted business method:

   import javax.interceptor.AroundInvoke;
   
   public class MethodProfiler {
   
       public MethodProfiler() {
       }
   
       @AroundInvoke
       private Object profile(InvocationContext invCtx)
           throws Exception {
       
           long t1 = System.nanoTime();
           Object result = invCtx.proceed();
           long t2 = System.nanoTime();
           System.out.println(invCtx.getMethod().getName() + 
                 "(" + invCtx.getParameters()[0] + ") took: " + 
                 ((t2 - t1)/ 1000.0) + " nano seconds.");
       
               return result;
       }
           
   }

Notice that the interceptor method meets the requirements previously listed.

Although the interceptor method in this example is defined in a separate class, it could have simply been added to the bean class itself. However, because method profiling has nothing to do with the basic purpose of the StringService, it makes sense to keep the interceptor in a separate class.

Adding an interceptor to a bean

Now that the MethodProfiler interceptor is defined, let's add it to StringAccountServiceBean. To do that, use the @Interceptors annotation in the bean -- the annotation specifies the interceptor class:

   import javax.interceptor.Interceptors;

   @Stateful
   @Interceptors({MethodProfiler.class})
   public class StringServiceBean
      implements StringServiceRemote {
      ...
       
   }

Now when the reverse method is invoked on StringServiceBean, the AroundInvoke interceptor method, profile, will print the time taken to complete the reverse method.

Interceptor chain and InvocationContext

You can chain together multiple interceptors to intercept a bean's methods. Use the @Interceptors annotation to identify a list of interceptors to associate with the bean (you can also specify the chain in the bean's deployment descriptor). The order that the interceptors are invoked is same as the order that they are specified in the @Interceptors annotation (or in the deployment descriptor).

At runtime, before a business method is invoked or a lifecycle event occurs, the container creates an instance of theInvocationContext object and passes it to each interceptor method. The InvocationContext interface provides a number of methods to get information about the business method such as the business method name or the parameters passed to the business method.

The proceed() method in InvocationContext causes the next interceptor method in the chain to be invoked. After the last AroundInvoke interceptor method is invoked, the proceed() method invokes the bean's business method. Interceptor methods must always call InvocationContext.proceed(). If the method is not called, no subsequent interceptor method, bean business method, or life cycle callback method will be invoked.

Using Interceptor chaining

You learned earlier that you can chain together multiple interceptors and use them to intercept a bean's methods. Let's use that capability to perform something interesting.

The reverse() method in StringServiceBean doesn't check if the argument is null. You can fix that by changing the reverse method in the bean. But you can use an interceptor to fix this. In fact, you can also use the interceptor to add some optimization. So let's define an interceptor, called StringServiceInterceptor, to do both things.

Rather than calling proceed() to continue the invocation flow, StringServiceInterceptor interceptor will first check if the argument is null or if the argument is a single character string. In both cases, the interceptor will simply return the argument.

Here is the StringServiceInterceptor:

   import javax.interceptor.AroundInvoke;
   import javax.interceptor.InvocationContext;

   public class StringServiceInterceptor {

       public StringServiceInterceptor() {
       }
   
       @AroundInvoke
       private Object validate(InvocationContext invCtx)
           throws Exception {
           Method m = invCtx.getMethod();
           if (m.getName().equals("reverse")) {
               String str = (String) invCtx.getParameters()[0];
               if ((str == null) || (str.length() < 2)) {
                   return str;
               }
           }
               
           return invCtx.proceed();
       }
           
   }
   

Adding chained interceptors to a bean

Now that the StringServiceInterceptor is defined, let's add it to StringServiceBean. To do that, use the @Interceptors annotation in the bean -- the annotation specifies the chained interceptor classes:

   import javax.interceptor.Interceptors;

   @Stateless
   @Interceptors({MethodProfiler.class, 
       StringServiceInterceptor.class})
   public class StringServiceBean
      implements StringServiceRemote {
      ...
       
   }
   

Running the Sample Code

  1. If you haven't already done so, download Java EE 5 SDK from the Java EE Downloads Page and install it.

  2. Download the sample package to the < appsrv_install> /samples/javaee5/enterprise directory, where < appsrv_install > is where you installed the Java EE 5 application server. Ensure that you set up your environment as described in the < appsrv_install> /samples/javaee5/index.html file.

  3. Extract the contents of the sample package. The interceptor-techtip-ear directory below ttoct2006intrcpt contains the source files and other support files for the sample.

  4. Change to the interceptor-techtip-ear.

  5. Start the Application Server by entering the following command: < appsrv_install > /bin/asadmin start-domain domain1

  6. Build and run the sample application. From the interceptor-techtip-ear directory enter the following command:
    ant all
    In response you should see output that includes the following:
       
          [exec] reverse(ABCD) ==> DCBA
          [exec] reverse(null) ==> null
          [exec] reverse(G) ==> G
          [exec] reverse(ABCDEFGHIJKLMNOPQRSTUVWXYZ) ==> ZYXWVUTSRQP
          ONMLKJIHGFEDCBA
    
     

Summary

EJB 3.0 interceptors give you an elegant way to extend the functionality of enterprise beans. They are especially useful for implementing crosscutting operations in a portable way.

The InvocationContext object that is passed to interceptors provides various methods that you can used to examine and alter the parameter values passed to a business method. The StringServiceInterceptor in the tip uses InvocationContext.getParameters() to perform few validation and optimization.

There are more advanced topics related to EJB interceptors, such as default interceptors, and class-level interceptors and method-level interceptors that allow a list of interceptors to be easily changed. These topics will be covered in a future Tech Tip.

You can learn more about EJB 3.0 interceptors in the following sample applications:

About the Author

Mahesh Kannan is member of the EJB container team. He has been involved with Java EE development for the last 6 years.

TECH TIPS SURVEY

Over the past year we've made some changes in the Enterprise Technologies Tech Tips. We'd like to get your feedback about those changes. Your responses will help us make the Tech Tips better serve your needs. Please go to the questionnaire at and give us your feedback.

INTRODUCTION TO SUN'S OPEN-SOURCE JAVA INITIATIVE

Sun Microsystems is open-sourcing its implementations of the Java platform. Find out what the buzz is, and what this means to you and to the future of Java technology. See http://java.sun.com/opensource/ for more details.

EJB 3.0 TRAINING

Want to learn more about the Java EE 5 platform and EJB 3.0? Now you can get the latest information directly from experts in the web-based course "Java EE 5 Platform and Enterprise JavaBeans 3.0 - An Expert-to-Engineer Session" .

DEVELOPER ASSISTANCE

Need programming advice on Java EE? Try Developer Expert Assistance

GLASSFISH CODE SAMPLES

Are you aware of the open source reference implementation of Java EE 5 called GlassFish? You can download GlassFish from the GlassFish Community Downloads page . Sample GlassFish applications are available on the GlassFish Samples page. You can contribute your own GlassFish applications to the set of samples .

Please read our Terms of Use and Licensing policies:

  • http://www.sun.com/share/text/termsofuse.html
  • http://developers.sun.com/dispatcher.jsp?uid=6910008
  • Rate and Review
    Tell us what you think of the content of this page.
    Excellent   Good   Fair   Poor  
    Comments:
    Your email address (no reply is possible without an address):
    Sun Privacy Policy

    Note: We are not able to respond to all submitted comments.