Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code 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: * The Java Persistence Query Language * EJB 3.0 Interceptors * Tech Tips Survey These tips were developed using the Java EE 5 SDK. You can download the SDK from the Java EE Downloads page (http://java.sun.com/javaee/downloads/index.jsp). You can view this issue of the Tech Tips on the Web at http://java.sun.com/mailers/techtips/enterprise/2006/TechTips_Oct06.html You can download the sample archive for the Java Persistence Query Language tip at: http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006jpql.zip. You can download the sample archive for the EJB 3.0 Interceptors tip at: http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006intrcpt.jar. Any use of this code and/or information below is subject to the license terms at http://developers.sun.com/dispatcher.jsp?uid=6910008. 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 (http://java.sun.com/javaee/technologies/entapps/persistence.jsp) 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 (http://java.sun.com/javaee/5/docs/tutorial/doc/PersistenceIntro.html#wp78460). A sample package (http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006jpql.zip) 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 (http://java.sun.com/javaee/downloads/index.jsp). 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 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 is 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 (http://java.sun.com/javaee/5/docs/tutorial/doc/QueryLanguage.html#wp80587). 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 (http://java.sun.com/javaee/downloads/index.jsp), and install it. 2. Set the following environment variables: o JAVAEE_HOME. This should point to where you installed the Java EE 5 SDK. o 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.) o 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 (http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006jpql.zip) 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 (http://jcp.org/en/jsr/detail?id=220) 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 (http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006intrcpt.jar) 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 an open source reference implementation of Java EE 5 called GlassFish. You can download GlassFish from the GlassFish Community Downloads page (http://java.sun.com/javaee/glassfish/getit.jsp). 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: o One one AroundInvoke method is allowed for each class. o It must have a no arg public constructor. o It must take an javax.interceptor.InvocationContext object as an argument and return a java.lang.Object object. o It can throw any application exception that is specified in the business interface, or any runtime exception. o It can be declared private, package private, protected, or public. o 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 the InvocationContext 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 (http://java.sun.com/javaee/downloads/index.jsp), and install it. 2. Download the sample package (http://java.sun.com/mailers/techtips/enterprise/2006/download/ttoct2006intrcpt.jar) to the directory, where is where you installed the Java EE 5 application server. Ensure that you set up your environment as described in the /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.getParamters() to perform few validation and optimization. There are more advanced topics related to EJB interceptors, such as DefaultInterceptors, 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: o The Interceptor-Stateless Session Bean Sample Application (https://glassfish-samples.dev.java.net/source/browse/*checkout*/glassfish-samples/ws/javaee5/enterprise/interceptor-stateless-ear/docs/index.html) o The Annotation-Override-ear Sample Application (https://glassfish-samples.dev.java.net/source/browse/*checkout*/glassfish-samples/ws/javaee5/enterprise/annotation-override-interceptor-ear/docs/index.html) 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 http://www2.sun.de/dc/forms/reg_xh_1710_250.jsp 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" (http://www.sun.com/training/catalog/courses/WJT-DTJ-1000.xml). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DEVELOPER ASSISTANCE Need programming advice on Java EE? Try Developer Expert Assistance (http://developers.sun.com/services/expertassistance/) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 (http://java.sun.com/javaee/glassfish/getit.jsp). Sample GlassFish applications are available on the GlassFish Samples page (https://glassfish-samples.dev.java.net/). You can contribute your own GlassFish applications to the set of samples (https://glassfish-samples.dev.java.net/new_samples.html). . . . . . . . . . . . . . . . . . . . . . . . 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 PRIVACY STATEMENT: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your e-mail preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe. * FEEDBACK Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: Enterprise_TechTips@sun.com * SUBSCRIBE/UNSUBSCRIBE Subscribe to other Java developer Tech Tips: - Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME). To subscribe to these and other JDC publications: - Go to the Sun Developer Network - Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to and click "Submit". - To unsubscribe, go to the Subscriptions page, (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click "Submit". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the Enterprise Java Technologies Tech Tips archives at: http://java.sun.com/developer/EJTechTips/index.html - COPYRIGHT Copyright 2006 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/developer/copyright.html Enterprise Java Technologies Tech Tips September 30, 2006 Trademark Information: http://www.sun.com/suntrademarks/ Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.