The Source for Java Technology Collaboration
User: Password:



Max Poon's Blog

J2EE Archives


Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 2 - Enabling JMX Monitoring on Hibernate v3 and Ehcache 1.3.0 on "HibernateTutorialApp"

Posted by maxpoon on June 27, 2007 at 09:12 AM | Permalink

Background

Apart from showing quite an example of easy enhancement of JSF-based web application as discussed earlier in "Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 1 - Co-ordinating Query Views Based on Parameter Passing from JSF View to Managed Bean", the NetBeans tutorial JSF-JPA-Hibernate application created ('SimpleJpaHibernateApp') can also be easily extended to demonstrate JMX monitoring on Hibernate v3 and the latest Ehcache 1.3.0 - the respective very common open source Java object/relational persistence and caching implementations, which have also included support for JMX to enable monitoring on their operational states and statistics, including the overall (e.g. query and 2nd level cache) cache hit/miss statistics, time of the slowest query (from Hibernate v3), and individual cache hit/miss statistics (from Ehcache 1.3.0).

These monitoring capabilities give very helpful information for application developers and administrators to understand actual behaviour of their applications deployed, for resource usage optimisation and performance tuning.

The following shows the configurations to use Ehcache 1.3.0, followed by configurations of JMX monitoring on Hibernate v3 and Ehcache 1.3.0, for both :

  1. Hibernate Core v3 applications
  2. JPA-with-Hibernate applications

Since JMX monitoring configuration :

  • on Ehcache requires the latest Ehcache 1.3.0
  • on JPA-with-Hibernate application is also based on the configuration for Hibernate Core v3 application

we shall proceed in the following sequence :

  1. Configuring HibernateTutorialApp/HibernateTravelPOJO to use Ehcache 1.3.0
  2. Configuring HibernateTutorialApp/HibernateTravelPOJO to enable JMX monitoring on Hibernate and Ehcache
  3. Configuring SimpleJpaHibernateApp to use Ehcache 1.3.0
  4. Configuring SimpleJpaHibernateApp to enable JMX monitoring on Hibernate and Ehcache
and (3) and (4) above will be discussed in next article "Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 3 - Enabling JMX Monitoring on Hibernate v3 and Ehcache 1.3, on SimpleJpaHibernateApp".

Software Requirements


(1) Configuring HibernateTutorialApp/HibernateTravelPOJO to use Ehcache 1.3.0

As HibernateTutorialApp actually uses another application 'HibernateTravelPOJO' supplied (in zip archive HibernateTravelPOJO.zip) in the tutorial for its persistence POJOs, configuration to use Ehcache 1.3.0 (instead of any earlier Ehcache version bundled with Hibernate) should be done in HibernateTravelPOJO.

Figure 1.0 - Contents of the HibernateTravelPOJO Project


There are 2 cases for the configuration of HibernateTravelPOJO to use Ehcache 1.3.0 :

Case 1 : If you have already set up HibernateTravelPOJO and HibernateTutorialApp according to the NetBeans tutorial "Using Hibernate With the NetBeans Visual Web Pack", you may just need to replace any previous version of the ehcache-*.jar file used (as specified in the Netbeans "Hibernate322" Class Library in the tutorial) with the ehcache-1.3.0.jar from Ehcache1.3.0 downloaded, then skip to Step 1.4 below.

Case 2 : If you are just going to set up HibernateTravelPOJO and HibernateTutorialApp, you can start with Step 1.1 below to set up HibernateTravelPOJO and HibernateTutorialApp to use the current latest Hibernate 3.2.4sp1 and Ehcache 1.3.0.

Step 1.1 - Create Hibernate Class Library in NetBeans IDE

First, follow the original NetBeans tutorial "Using Hibernate With the NetBeans Visual Web Pack" section "Create a Hibernate Class Library" to create Class Library for Hibernate in NetBeans IDE.  Since the latest Hibernate 3.2.4.sp1 is available (as at time of writing this article), it can be used in place of Hibernate 3.2.2 suggested in the tutorial, also referring to Figure 1.1 below for the list of jar files to be included for the NetBeans Class Library "Hibernate3.2.4sp1".

Figure 1.1 - Create HibernateCore3.2.4sp1 Class Library in NetBeans IDE


Step 1.2 - Create "Ehcache-1.3.0" Class Library  in NetBeans IDE

Download the latest Ehcache 1.3.0 (now including JMX support) from here, unzip it, and create new NetBeans Class Library "Ehcache-1.3.0" as shown:

Figure 1.2.1 - Create new "Ehcache-1.3.0" Class Library for NetBeans IDE


Figure 1.2.2 - Add ehcache-1.3.0.jar to the new "Ehcache-1.3.0" Class Library


Step 1.3 - Configure HibernateTravelPOJO to use Ehcache 1.3.0

Follow the steps of "Using Hibernate With the NetBeans Visual Web Pack" to set it up, but with the our new NetBeans Class Libraries "HibernateCore3.2.4sp1" and "Ehcache-1.3.0" as follows.

Figure 1.3 - Add Library "Ehcache-1.3.0" to HibernateTravelPOJO


Step 1.4 - Configure Hibernate to use Ehcache 1.3.0

To configure Hibernate v3 to use Ehcache 1.3.0, we shall start with modifying the Hibernate configuration file hibernate.cfg.xml as shown in Code Listing 1.4.1 (additional settings required in bold) to enable Hibernate 2nd-level caching using Ehcache on the following classes and collection for HibernateTravelPOJO :
  • Class hibernatetravelpojo.Person with read-write mode
  • Class hibernatetravelpojo.Trip with read-write mode
  • Collection hibernatetravelpojo.Person.trips with read-write mode
Meanwhile, we also need to modify the Hibernate mapping files
  • hibernatetravelpojo/Person.hbm.xml
  • hibernatetravelpojo/Trip.hbm.xml
as shown in Code Listing 1.4.2 and Code Listing 1.4.3 (again, additional settings required in bold).

Code Listing 1.4.1 - Modified hibernate.cfg.xml for HibernateTravelPOJO
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
  "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

  "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

  <session-factory>


    <!-- Data Source Name -->
    <property name="hibernate.connection.driver_class">
        org.apache.derby.jdbc.ClientDriver</property>

    <property name="hibernate.connection.url">
        jdbc:derby://localhost:1527/travel</property>

    <property name="hibernate.connection.username">travel</property>
    <property name="hibernate.connection.password">travel</property>

    <!-- SQL dialect -->
    <property name="hibernate.dialect">
        org.hibernate.dialect.DerbyDialect</property>


    <!-- Debug logging of SQL statements -->
    <property name="hibernate.show_sql">true</property>

    <!-- Enable Hibernate's automatic session context management -->
    <property name="hibernate.current_session_context_class">thread</property>

    <!-- Enable Hibernate statistics generation -->
    <property name="hibernate.generate_statistics">true</property>


    <!-- Cache Configurations -->
    <!-- Using net.sf.ehcache.hibernate.SingletonEhCacheProvider instead of
         net.sf.ehcache.hibernate.EhCacheProvider ensures the same instance
         of CacheManager is referred to by both Hibernate and our JMX Agent

         simpleJpaHibernateApp.agents.jmxAgent. (Thanks to Greg Luck!)  -->
    <property name="hibernate.cache.provider_class">
        net.sf.ehcache.hibernate.SingletonEhCacheProvider</property>

    <property name="hibernate.cache.provider_configuration">
        /ehcache.cfg.xml</property>

    <property name="hibernate.cache.use_minimal_puts">false</property>
    <property name="hibernate.cache.use_query_cache">true</property>
    <property name="hibernate.cache.use_second_level_cache">true</property>
    <property name="hibernate.cache.use_structured_entries">true</property>

    <!-- Mapping Files -->
    <mapping resource="hibernatetravelpojo/Person.hbm.xml"/>
    <mapping resource="hibernatetravelpojo/Trip.hbm.xml"/>
       
  </session-factory>

</hibernate-configuration>


Coding Listing 1.4.2 - Modified Person.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>


  <class name="hibernatetravelpojo.Person" table="PERSON">


    <!-- Class Cache -->

    <cache usage="read-write"/>

    <!-- Primary Key -->

    <id name="personId" column="PERSONID">
      <generator class="increment"/>
    </id>
       
    <!-- Direct Data Properties -->
    <property name="name"
            column="NAME"/>
    <property name="jobTitle"
            column="JOBTITLE"/>
    <property name="frequentFlyer"
            column="FREQUENTFLYER"/>
       
    <!-- Relationship References -->
    <set name="trips" table="TRIP" lazy="true" inverse="true"
        cascade="all-delete-orphan">
      <!-- Collection Cache -->
      <cache usage="read-write"/>
      <key column="PERSONID"/>
      <one-to-many class="hibernatetravelpojo.Trip"/>
    </set>

  </class>

</hibernate-mapping>

Code Listing 1.4.3 - Modified Trip.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
   
  <class name="hibernatetravelpojo.Trip" table="TRIP">
       
    <!-- Class Cache -->
    <cache usage="read-write"/>
       
    <!-- Primary Key -->
    <id name="tripId" column="TRIPID">
      <generator class="increment"/>
    </id>
       
    <!-- Direct Data Properties -->
    <property name="personId"
            column="PERSONID"/>   
    <property name="depDate"
            column="DEPDATE"
              type="date"/>
    <property name="depCity"
            column="DEPCITY"/>
    <property name="destCity"
            column="DESTCITY"/>
    <property name="tripTypeId"
            column="TRIPTYPEID"/>
       
    </class>
   
</hibernate-mapping>

Also, the following Ehcache configuration file ehcache.cfg.xml is required in the root of Java source directory together with hibernate.cfg.xml.

Code Listing 1.4.4 - ehcache.cfg.xml for HibernateTravelPOJO
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">
  <diskStore path="java.io.tmpdir"/>
  <defaultCache
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    overflowToDisk="true"
    diskPersistent="true"
    diskExpiryThreadIntervalSeconds="120"
    memoryStoreEvictionPolicy="LRU"
  />
  <cache name="hibernatetravelpojo.Person"
         maxElementsInMemory="300"
         eternal="true"
         overflowToDisk="false"
  />
  <cache name="hibernatetravelpojo.Trip"
         maxElementsInMemory="300"
         eternal="true"
         overflowToDisk="false"
  />
  <cache name="hibernatetravelpojo.Person.trips"
         maxElementsInMemory="300"
         eternal="true"
         overflowToDisk="false"
  />
</ehcache>

With the above configuration, compile the project HibernateTutorialApp and run in NetBeans IDE, right-click on HibernateTutorialApp project and select [Clean and Build], then [Deploy Project], followed by [Run Project], giving the following

Figure 1.4 - HibernateTutorialApp with inital Trip query for Person "Able, Tony"


(2) Configure JMX Monitoring for HibernateTutorialApp/HibernateTravelPOJO

Enabling JMX monitoring for an application typically involves the creation of following :
  • JMX MBean - which can represent a device, an application, or any resource that needs to be managed.
  • JMX Agent - which is an application that registers MBean(s) with a MBeanServer, e.g. the platform MBeanServer.
In case of Hibernate, the MBean is provided via :
followed by MBean registration via :
where objectName is your assigned registration ObjectName for statMBean on the instance of MBeanServer.

In case of Ehcache, four types of 'pre-fabricated' MBeans have already been provided, including :
  • CacheManager MBean
  • Cache MBean
  • CacheConfiguration MBeans
  • CacheStatistics MBeans
together with net.sf.ehcache.management.ManagementService which provides convenient registration (instead of individual handling) of all these four MBeans for the instance of CacheManager being used :
where registerCacheManger, registerCache, registerCacheConfiguration, registerCacheStatistics are the boolean flags to indicate whether the respective MBeans are to be registered, under the ObjectName domain net.sf.ehcache.

Hence, to enable JMX monitoring on Hibernate and Ehcache, we simply need to create a JMX Agent that registers the appropriate Hibernate and Ehcache MBeans with our MBeanServer.

Step 2.1 - Create JMX Agent with Hibernate and Ehcache MBeans Registration Codes

First, open the HibernateTravelPOJO project in NetBeans IDE, and create our new JMX Agent (as shown in Figure 2.1.1), specifying it as "JmxAgent" inside the package "hibernatetravelpojo.agents" (as shown in Figure 2.1.2), followed by Adding JMX registration code (as shown in Figure 2.1.3).

Figure 2.1.1 - Creating file with type "JMX Agent" under "Management" Category


Figure 2.1.2 - Specifying the new 'JmxAgent' in package 'hibernatetravelpojo'


Figure 2.1.3 - Adding MBeans registration codes for Hibernate and Ehcache in JmxAgent


The additional MBean registration codes are highlighted in bold in the following code listing.

Also, we need to modify the getDefault method (also as highlighted in bold) to take the SessionFactory as parameter from the calling context instead of getting it again.

Code Listing 2.1 - JmxAgent.java (Hibernate and Ehcache MBeans registration codes in bold)
/*
 * JmxAgent.java
 *
 */

package hibernatetravelpojo;

import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.management.ManagementService;
import org.hibernate.SessionFactory;
import org.hibernate.jmx.StatisticsService;

/**
 * JMX agent (singleton) for monitoring Hibernate and Ehcache in the
 *  'extended' HibernateTravelPOJO example application from NetBeans
 *  tutorial "Using Hibernate With the NetBeans Visual Web Pack"
 *  at http://www.netbeans.org/kb/55/vwp-hibernate.html, which uses:
 * <ul>
 * <li>JavaServer Faces (JSF) web-tier</li>
 * <li>Hibernate Core (3.2.4.sp1)</li>
 * <li>Ehcache 1.3.0</li>
 * </ul>
 *
 * @author Max Poon (maxpoon@dev.java.net)
 */
public class JmxAgent {
   
  /**
   * Register MBeans, enable Hibernate & Ehcache JMX Statistics
   * @param sf org.hibernate.SessionFactory to be passed in from
   *           the invoking context (instead of creating it again
   *           here which is expensive operation)
   */
  public void init(SessionFactory sf) throws Exception {
     
    // Define ObjectName of the MBean
    ObjectName on = new ObjectName
      ("Hibernate:type=statistics,application=HibernateTravelPOJO");


    // Enable Hibernate JMX Statistics
    StatisticsService statsMBean = new StatisticsService();
    statsMBean.setSessionFactory(sf);
    statsMBean.setStatisticsEnabled(true);
    mbs.registerMBean(statsMBean, on);
       
    /**
     * Enable Ehcache JMX Statistics
     * Use CacheManager.getInstance() instead of new CacheManager()
     * as net.sf.ehcache.hibernate.SingletonEhCacheProvider is used
     * to ensure reference to the same CacheManager instance as used
     * by Hibernate
     */
    CacheManager cacheMgr = CacheManager.getInstance();
    ManagementService.registerMBeans
      (cacheMgr, mbs, true, true, true, true);
  }
   
  /**
   * Returns an agent singleton.
   */
  public synchronized static JmxAgent
getDefault(SessionFactory sf)
      throws Exception {
    if(singleton == null) {
      singleton = new JmxAgent();
     
singleton.init(sf);
    }
    return singleton;
  }
   
  public MBeanServer getMBeanServer() {
    return mbs;
  }
   
  // Platform MBeanServer used to register your MBeans
  private final MBeanServer mbs =
      ManagementFactory.getPlatformMBeanServer();
   
  // Singleton instance
  private static JmxAgent singleton;
}


Step 2.2 - Modify HibernateUtil.java to initiate JMX Agent

We need to instantiate the JMX Agent somewhere, in case applications such as HibernateTravelPOJO which uses hibernatetravelpojo.HibernateUtil to get the Hibernate SessionFactory, HibernateUtil is a good place to do it.


Code Listing 2.2 - hibernatetravelpojo.HibernateUtil.java with modifications needed in bold
package hibernatetravelpojo;

import org.hibernate.*;
import org.hibernate.cfg.*;

/**
 * HibernateUtil.java of HibernateTravelPOJO example application from
 * NetBeans tutorial "Using Hibernate With the NetBeans Visual Web Pack"
 * at http://www.netbeans.org/kb/55/vwp-hibernate.html
 *
 * @author Max Poon (maxpoon@dev.java.net)
 */
public class HibernateUtil {
   
  private static final SessionFactory sf;
   
  static {
    try {
      // Create the SessionFactory
      sf = new Configuration().configure().buildSessionFactory();
      // Initiate JMX Agent
      JmxAgent.getDefault(sf);
    } catch (Throwable ex) {
      // Ensure you log the exception somehow, as it might be swallowed
      System.err.println("Initial SessionFactory creation failed." + ex);
      throw new ExceptionInInitializerError(ex);
    }
  }
   
  public static SessionFactory getSessionFactory() {
    return sf;
  }
   
}

Now, HibernateTutorialApp can be recompiled by right-clicking on [HibernateTutorialApp] project in NetBeans IDE and selecting [Clean and Build Project] (which automatically recompiles HibernateTravelPOJO), and tested by :
  • requesting http://localhost:port/HibernateTutorialApp in browser, or
  • right-click on the [HibernateTutorialApp] project in NetBeans IDE and select [Run Project]
to observe that shown in Figure 1.4 again, and to try querying the Trip's for the other Person's.

The application should now be executing successfully so it is ready for observation of JMX metrics in the following Step 2.3.

In case of problems, the application server server log can be checked.

Step 2.3 - Use JConsole to Observe JMX Statistics

Start JConsole and connect to the JVM running GlassFish (indicated by "com.sun.enterprise.server.PELaunch" in JConsole) via JMX Dynamic Attach :

Figure 2.3.0 - JConsole attaching to JVM running GlassFish (com.sun.enterprise.server.PELaunch) via JMX Dynamic Attach


Go to JConsole [MBeans] and expand on [Hibernate>statistics>HibernateTravelPOJO>Attributes] to view Hibernate Statistics (as shown in Figure 2.3.1) which include :
  • Collection Statistics
    • Role Names, Fetch Count, Load Count, Recreate Count, Remove Count, Update Count
  • Entity Statistics
    • Entity Names, Delete Count, Fetch Count, Insert Count, Load Count, Update Count
  • Query Statistics
    • Queries, Hit Count, Miss Count, Cache Put Count, Execution Count, Execution Max Time, Execution Max Time Query String
  • Second Level Cache Statistics
    • Cache Region Names, Cache Hit Count, Cache Miss Count, Cache Put Count
  • Session and Transaction Counts
    • SessionFactory JNDI Names, Session Open Count, Session Close Count, Transaction Count

Figure 2.3.1 - JConsole showing Hibernate Statistics for HibernateTravelPOJO


Note that the statistics as shown in the Figure 2.3.1 agrees with the HibernateTutorialApp's invocation of initial query page which retrieves 5 Trip instances for the "Able, Tony" Person instance of  as shown in Figure 1.4, :

* EntityLoadCount = 11
   => HibernateTutorialApp's Page1#prerender() (as shown in Code Listing 2.3.1) invoking :
  • SessionBean1.getPersonOptions()
    to load all 6 available instances of Person available.
  • SessionBean1.getTripDataProvider().refreshTripsList(
       new Integer((String)firstPerson.getValue()) )

    to load all 5 available instances of Trip available for the "Able, Tony" Person instance
* CollectionLoadCount = 1
   => HibernateTutorialApps's Page1#prerender() (as shown in Code Listing 2.3.1) invoking :
  • SessionBean1.getTripDataProvider().refreshTripsList(
       
    new Integer((String)firstPerson.getValue()) )
    to load 1 collection of  instances of Trip available for the "Able, Tony" Person instance
* SecondLevelCachePutCount = EntityLoadCount + CollectionLoadCount = 12
  • as the loaded entities and collection are put into the respective caches

Code Listing 2.3.1 - Page1#prerender() method
  ...
  public void prerender() {

    try {
      if (dropDown1.getSelected() == null ) {
        SessionBean1 sb1 = getSessionBean1();
        if (sb1 == null) {
          log("Null \"SessionBean1\" from Page1#prerender()");
        } else {
          Option[] personOptions = sb1.getPersonOptions();
          if (personOptions == null) {
            log("Null \"PersonOptions\" from Page1#prerender()");
          } else {
            Option firstPerson = personOptions[0];
            sb1.getTripDataProvider().refreshTripsList(
                new Integer((String)firstPerson.getValue()));
          }
        }
      }
    } catch (Exception ex) {
      log("Error Description", ex);
      error(ex.getMessage());
    }
  }
  ...

The cache configurations for each of the Entity Cache are available via the Ehcache CacheConfiguration MBeans, e.g. as shown for Person in Figure 2.3.2.

Figure 2.3.2 - JConsole showing Ehcache CacheConfiguration for hibernatetravelpojo.Person


While, individual cache statistics (e.g. for entity, collection, query, and 2nd level cache) should be available programmatically from Hibernate JMX statistics, e.g.
various cache statistics are available directly from Ehcache as MBean attributes for display by JConsole without need for further programmatic retrieval, as shown in
  • Figure 2.3.3 for class cache for Person
  • Figure 2.3.4 for class cache for Trip
  • Figure 2.3.5 for collection cache for PersonTrip
  • Figure 2.3.6 for StandardQueryCache (showing it not being used)
  • Figure 2.3.7 for Ehcache Cache and CacheManager Operations
    • per Cache operations : flush(), removeAll()
    • CacheManager operations : clearAll(), getCache(), shutdown()

Continue Reading...



Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 1 - Co-ordinating Query Views Based on Parameter Passing from JSF View to Managed Bean

Posted by maxpoon on June 13, 2007 at 12:47 PM | Permalink

Background

The NetBeans tutorial "Using Hibernate With the Java Persistence API" nicely demonstrates, by using the NetBeans IDE, easy construction of :
  • Java Persistence API (JPA) entity classes from given database schema (using NetBeans IDE bundled 'sample' database)
  • JavaServer Faces (JSF) CRUD application (on the 'sample' database) using JPA and entity classes generated above
The follow-up tutorial "NetBeans Wiki - UsingHibernateWithJPA" further shows usage of JPA as well as Hibernate-specific facilities, including :
While the above-mentioned two tutorials demonstrate ease of JSF-JPA-Hibernate application construction with usage of JPA and Hibernate facilities in the persistence-tier, it is interesting to explore also on the flexibility and power of JSF in the web-tier.

As the resulting created application provides individual CRUD functions to the actually mutually-associated entity classes, it makes sense to see how, using JSF, we can, easily in the two steps described, extend the application to add new web query views from the CRUD view of one entity class to list the links to CRUD views of its associated entity classes with re-use of existing individual CRUD views as much as possible.  This is an example of co-ordinating query views based on parameter passing from JSF View to Managed Bean.  Adding a 'new view' to list all Products associated with a selected ProductCode via the ProductCode-Product one-to-many association is used here.

Extending The Sample Application

Based on the "Using Hibernate With the Java Persistence API" tutorial (but choosing the sample database tables ProductCode, Product, and Manufacturer instead of Customer and DiscountCode), our JSF-JPA-Hibernate sample application (let's call it 'SimpleJpaHibernateApp') can be created.  (Please refer to the original tutorial for other details of software needed and environment set-up).

Figure 1 - 'Sample' Database Tables to be used by SimpleJpaHibernateApp
SimpleJpaHibernateApp-DBSchema.png

At this time, our SimpleJpaHibernateApp created provides individual CRUD functions to the three actually associated entity classes (e.g. both ProductCode-Product and Manufacturer-Product having one-to-many associations).

Here, we shall pick the ProductCode->Product navigation (i.e. enquiring on all Products for CRUD, given a selected ProductCode) since there are 6 ProductCodes and 30 Products (so it's in average 1:5 ratio) from the out-of-the-box NetBeans 'sample' database.

In this case, our application (and the targeted new query function) should be as follows, starting with its home page with the HTML links to :
  • "List of ProductCode" - listing all the ProductCodes records (in the 'sample' database)
  • "List of Product" - listing all the Products records
  • "List of Manufacturer" - listing all the Manufacturer records

Figure 2 - SimpleJpaHibernateApp Home Page
SimpleJpaHibernateApp-Home0.png

Let's start the query navigation with ProductCode by clicking on the "List of ProductCode" link to lists all the ProductCodes :

Figure 3 - "Listing ProductCodes" Page
SimpleJpaHibernateApp-ProdCodeList0.png

Then, click on one of the ProductCodes, e.g. "SW" in ProdCode column, the "Detail of ProdCode" for ProdCode "SW" will be displayed :

Figure 4 - "Detail of ProductCode" Page
SimpleJpaHibernateApp-ProdCodeDetail0.png

Now, it would be nice if we can add a HTML link "List of Product with this ProductCode" (highlight in red box) to display all the Products of this selected ProductCode with ProdCode="SW" :

Figure 5 - New (Desired) "Detail of ProductCode" Page
SimpleJpaHibernateApp-ProdCodeDetail1.png

So that clicking on "List of Product with this ProductCode" will display :

Figure 6 - New (Desired) "Listing Products" Page
SimpleJpaHibernateApp-SelectedProdList-SW.png


This can be done easily in JSF

An quite obvious way is to get the selected ProdCode in the "Detail of ProductCode" page, stored in JSF EL :
  • #{productCode.productCode.prodCode}
as shown in the Coding Listing 1, for reference by the subsequently invoked action
  • <h:commandLink action="productCode_list" value="Show All ProductCode"/>
which is simpleJpaHibernateApp/web/product/List.jsp (shown in the following Code Listing 2) as defined in faces-config.xml.

Code Listing 1 - Original "Detail of ProductCode" Page -
Location: simpleJpaHibernateApp/web/productCode/Detail.jsp

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>SimpleJpaHibernateApp - Detail of ProductCode</title>
  </head>
  <body>
    <f:view>
      <h:messages errorStyle="color: red" infoStyle="color: green"
                  layout="table"/>

      <h1>Detail of productCode</h1>
      <h:form>
        <h:panelGrid columns="2">
          <h:outputText value="ProdCode:"/>
          <h:outputText title="ProdCode"
           
value="#{productCode.productCode.prodCode}"/>
          <h:outputText value="DiscountCode:"/>
          <h:outputText title="DiscountCode"
            value="#{productCode.productCode.discountCode}"
/>
          <h:outputText value="Description:"/>
          <h:outputText title="Description"
           
value="#{productCode.productCode.description}"/>
        </h:panelGrid>
        <h:commandLink action="productCode_edit" value="Edit" />
        <br>
        <h:commandLink action="productCode_list"
          value="Show All ProductCode"/>
        <br>
        <a href="/SimpleJpaHibernateApp/index.jsp">Back to Home Page</a>
      </h:form>
    </f:view>
  </body>
</html>

Now, let's look at how the subsequent "List of Product" works :

Code Listing 2 - "List of Product" Page -
Location: simpleJpaHibernateApp/web/product/List.jsp
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<html>
  ...

  <body>
    <f:view>
      <h:messages errorStyle="color: red" infoStyle="color: green"
        layout="table"/>

      <h1>Listing Products</h1>

      <h:form>
      <h:commandLink action="#{product.createSetup}"
        value="New Product"/>

      <br>
      <a href="/SimpleJpaHibernateApp/index.jsp">Back to Home Page</a>
      <br>
      <h:outputText value=
        "Item #{product.firstItem + 1}..#{product.lastItem} of #{product.itemCount}"/>
      &nbsp;

      <h:commandLink action="#{product.prev}"
        value="Previous #{product.batchSize}"
        rendered="#{product.firstItem >= product.batchSize}"/>
      &nbsp;

      <h:commandLink action="#{product.next}"
        value="Next #{product.batchSize}"
        rendered="#{product.lastItem + product.batchSize <=
        product.itemCount}"/>
      &nbsp;

      <h:commandLink action="#{product.next}"
        value="Remaining #{product.itemCount - product.lastItem}"
        rendered="#{product.lastItem < product.itemCount &&
        product.lastItem + product.batchSize > product.itemCount}"/>

      <h:dataTable value='#{product.products}' var='item' border="1"
          cellpadding="2" cellspacing="0">

      ...
      </h:dataTable>
      </h:form>
    </f:view>
  </body>
</html>

It can be observed that Products and ItemCount are retrieved from the JSF Expressions :
  •  #{product.products} which binds to the getProduct() method
  •  #{product.itemCount} which binds to the getItemCount() method
of the managed bean simpleJpaHibernate.controller.ProductController.
[Note: Examing ProductController.java shows that the other #{product.firstItem} and  #{product.lastItem} actually derive from #{product.itemCount}.]

Hence, if we can have these two methods be aware of the selected ProdCode to :
  • get only Products with the selected Prodcode
  • count only Products with the selected ProdCode
respectively, we shall be able to re-use simpleJpaHibernateApp/web/product/List.jsp to display a table of Products with the selected ProdCode.

Step 1 - Add New JSF CommandLink in "Detail of ProducCode" Page to Pass the Selected ProdCode to New Bean Method in ProductController

Using the added <h:commandLink ...> (highlighted below), the selected ProdCode in #{productCode.productCode.prodCode} can be passed as JSF FacesContext parameter selectedProdCode from simpleJpaHibernateApp/web/productCode/Detail.jsp to simpleJpaHibernate.controller.ProductController :

Code Listing 3 - New <h:commandLink...> added to "Detail of ProductCode" page -
simpleJpaHibernateApp/web/productCode/Detail.jsp
  ....
  <h:commandLink action="productCode_list"

                  value="Show All ProductCode"/>
  <br>
  <h:commandLink action="#{product.setSelectedProdCodeDisplayProducts}"
      value="List of Product with this ProductCode">
    <f:param name="selectedProdCode"
        value="#{productCode.productCode.prodCode}"/>
  </h:commandLink>
  <br>
  <a href="/SimpleJpaHibernateApp/index.jsp">Back to Home Page</a>
  ....

Step 2 - Add New Bean Property and Method, and Modify Related Bean Methods in ProductController

The 'passed-in' FacesContext selectedProdCode parameter is to be retreived by ProductController with the following new method setSelectedProdCodeDisplayProducts()

Code Listing 4 - New bean methods setSelectedProdCode(String selectedProdCode) and
setSelectedProdCodeDisplayProducts() of simpleJpaHibernateApp.controller.ProductController

  /**
   * Setter for selectedProdCode property
   * @param selectedProdCode String PROD_CODE column value of the selected
   *                         ProducCode e.g. in productCode/Detail.jsp
   */
  public void setSelectedProdCode(String selectedProdCode) {
     this.selectedProdCode = selectedProdCode;
  }

  /**

   * setSelectedProdCodeDisplayProducts() :
   * <ol>
   * <li>Get 'selectedProdCode' parameter from FacesContext</li>
   * <li>Set bean property 'selectedProdCode' with the parameter value</li>
   * <li>Return "product_list" to invoke product/List.jsp as defined in
   *     faces-config.xml to display Products with this selectedProdCode</li>
   * </ol>
   */
  public String setSelectedProdCodeDisplayProducts() {

     String _selectedProdCode =
        (String) FacesContext.getCurrentInstance()

           .getExternalContext().getRequestParameterMap()
           .get("selectedProdCode");


     setSelectedProdCode(_selectedProdCode);

     return "product_list";
  }


Also, the getProducts() and getItemCount() methods are required to be modified to get the 'selectedProdCode-aware' query behaviour :

Code Listing 5 - Modified managed bean methods getProducts() and getItemCount() of simpleJpaHibernateApp.controller.ProductController

  /**
   * Retrieve Products and return as ListDataModel according to:
   * <ol>
   *   <li>ProductCode selected already
   *     <code>
   *     (selectedProdCode != null  && selectedProdCode.length() != 0)
   *     </code>
   *     => retrieve Products with the selected ProductCode
   *   </li>
   *   <li>ProductCode not selected (otherwise)
   *     => retrieve all Products
   *   </li>
   * </ol>
   */

  public DataModel getProducts() {
     EntityManager em = getEntityManager();
     try{
        Query q;
        if (selectedProdCode != null && selectedProdCode.length() != 0) {
           q = em.createQuery("select object(o) from Product as o " +
               "where o.productCode.prodCode = :selectedProdCode")
               .setParameter("selectedProdCode", selectedProdCode);
        } else {
           q = em.createQuery("select object(o) from Product as o");
        }
        q.setMaxResults(batchSize);
        q.setFirstResult(firstItem);
        model = new ListDataModel(q.getResultList());
        return model;
     } finally {
        em.close();
     }
  }

  /**
   * Count no. of Products items according to:
   * <ol>
   *   <li>ProductCode selected already
   *     <code>
   *     (selectedProdCode != null  && selectedProdCode.length() != 0)
   *     </code>
   *     => count number of Products with the selected ProductCode
   *   </li>
   *   <li>ProductCode not selected (otherwise)
   *     => count number of all Products
   *   </li>
   * </ol>
   */
  public int getItemCount() {
     EntityManager em = getEntityManager();
     try{
        Query q;
        if (selectedProdCode != null && selectedProdCode.length() != 0) {
           q = em.createQuery("select count(o) from Product as o " +
               "where o.productCode.prodCode = :selectedProdCode")
               .setParameter("selectedProdCode", selectedProdCode);
        } else {
           q = em.createQuery("select count(o) from Product as o");
        }
        int count = ((Long) q.getSingleResult()).intValue();
        return count;
     } finally {
        em.close();
     }
  }


Some Final Steps - Related to index.jsp

Basically, the addition of new query based on the selected ProdCode (as criteria) passed from previous "Detail of ProductCode" JSF page / view (based also on modification/re-use of existing "Listing Products" view) is completed.

However, also due to the introduction of new query behaviour to the "Listing of Products" view, there are a few subsequent amendments to the application needed.

This includes the requirement to reset the newly introduced bean property selectedProdCode of ProductController in case of invocation from the application home page, to display all available Products in the database as expected.  Again, this can be done by the very same JSF CommandLink, this time with "" for the selectProdCode parameter :
  • <h:commandLink value="List of Product"
        action="#{product.setSelectedProdCodeDisplayProducts}">
      <f:param name="selectedProdCode" value=""/>
    </h:commandLink>
Code Listing 6 - Modified index.jsp
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<%--
  index.jsp -  Modified from index.jsp_orig to use JSF <h:commandLink ...>
  to clear any previously set ProductController#selectedProdCode for listing
  Products via 'product_list' action
  @author Max Poon (maxpoon@dev.java.net)
--%>

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Simple JPA Hibernate Application</title>
    </head>
    <body>
        <f:view>
            <h:messages errorStyle="color: red" infoStyle="color: green"
                    layout="table"/>

            <h1>Simple JPA Hibernate Application</h1>
            <h:form>
                <br/>
                <h:commandLink value="List of ProductCode"
                        action="productCode_list"/>
                <br/>
                <h:commandLink value="List of Product"
                        action="#{product.setSelectedProdCodeDisplayProducts}">
                    <f:param name="selectedProdCode" value=""/>
                </h:commandLink>
                <br/>
                <h:commandLink value="List of Manufacturer"
                        action="manufacturer_list"/>
            </h:form>
        </f:view>
    </body>
</html>



This requires configuration / invocation of the new index.jsp as JSF View via the application's web.xml servlet-mapping  :

Code Listing 7 - Example new servlet-mapping required in web.xml
    ....
    <servlet-mapping>

        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>
    ....

with all correspondingly links to the SimpleJpaHibernateApp home page modified from index.jsp to index.jsf, i.e. :

Code Listing 8 - Modified href link to "Home Page" in all referencing JSP's
    ....
    <%-- Modified link

         from /SimpleJpaHibernateApp/index.jsp
           to /SimpleJpaHibernateApp/index.jsf
    --%>
    <a href="/SimpleJpaHibernateApp/index.jsf">Back to Home Page</a>
    ....

For completeness' sake in this case, we should also have an index.html (suggested to be set as welcome-file in web.xml) so that requesting :
  • http://host:port/simpleJpaHibernateApp/
will be automatically redirected to the correct SimpleJpaHibernateApp home page
  • http://host:port/simpleJpaHibernateApp/index.jsf.

Done, Ready for Compilation, Deployment and Testing

So, SimpleJpaHibernateApp is now ready for compilation, deployment and testing, e.g. by clicking "List Product with this ProductCode" from "Detail of ProductCode" page with ProductCode as, e.g. "HW", and you should get :

Figure 6 - "Listing Products" Page for "HW" ProductCode
SimpleJpaHibernateApp-SelectedProdList-HW.png

while clicking "List of Product" on the Home Page, you should get all the available Products for ProdCode "SW", "HW", etc.

Figure 7 - "Listing Products" Page for All ProductCodes, i.e. All Available Products
SimpleJpaHibernateApp-SelectedProdList-ALL.png


If you can get the above query views working, congratulations!  Your SimpleJpaHibernate should be working fine, and ready for your further exploration!

Coming Next

In coming (hopefully soon) "Extending the NetBeans Tutorial JSF-JPA-Hibernate Application, Part 2", I would like to share on enabling JMX monitoring on Hibernate 3.2.x, and on the latest Ehcache 1.3.0 with full JMX support and detail per-cache monitoring statistics (yes, it's great!) - using our SimpleJpaHibernateApp as example (thanks to Ehcache's creator and maintainer Greg Luck for his helping me get the JMX monitoring on Ehcache working for the SimpleJpaHibernateApp, when we met at JavaOne May this year)

Stay tuned!



JavaOne2007 Hands-On Lab 1420

Posted by maxpoon on May 19, 2007 at 12:09 AM | Permalink

My colleagues Paul Cheung, Luis-Miguel Alventosa and myself together developed, and presented for, this year's JavaOne Hands-On Lab (HOL) 1420 on "Non-instrusive Monitoring and Troubleshooting of Java Applications using Java Management Extension (JMX), JConsole and Aspect-Oriented Programming (AOP)" at Moscone Center, San Francisco. This lab session consists of two main parts :
  • deployment and monitoring of a sample Spring Web MVC application using Glassbox, all running on GlassFish - with monitored metrics presented both via (a) Glassbox's AJAX-based Web Client, and (b) JConsole Plugin to do real-time derivation on collected JMX metrics from Glassbox and dynamic charting
  • troubleshooting of Java application using JConsole
The technologies we demonstrated include: We are also very glad to have the support during development of the lab contents, as well as the presence, short speech, and blog by Ron Bodkin, creator of Glassbox and CTO of Glassbox Corporation, during this JavaOne lab session.

We had a full registration of ~140 attendees, and hope everybody had enjoyed the lab.  And of course, I'd like to thank all our attendees for coming and our colleagues who had supported and helped us during the whole cycle of the lab contents development and delivery.

Links and Resources
Please find our JavaOne lab session abstract and instructions [to be updated with the latest lab instructions used at JavaOne (which further enable Glassbox's JDBC connections and statements execution monitoring on GlassFish) probably in 1-2 weeks' time], and the following resource links for more information: Note: The non-instrusive monitoring and metrics collection mechanism itself (using AOP and potentially Java Annotations) is an interesting and elaborated topic.

Some screen shots?
Visual presentations are usually more impacting, so I enclose the following screen captures....

Sample Glassbox AJAX-based Web Client screen capture highlighting "SLOW" operation of the SimpleSpringApp ("Hands-On Lab Spring") application (Web Client upper portion) with analysis (Web Client lower porton) at our lab session :


glassbox-webclient.png

Sample screen capture showing metrics of monitored operations displayed in JavaSE 7 JConsole (with tabular navigation of children entries and the attributes of each child entry, shown in 'Attribute value' window on the right) :


glassbox-jconsole.png