Statistics via JMX

In this section, we will discuss how you can collect Hibernate statistics via JMX. But first, we will briefly discuss the core concepts in JMX in case you have never used it.

Introduction to JMX

Java Management Extension (JMX) was officially added to the Java Standard Edition (J2SE) since version 5. At the core of its architecture is the MBean server, also known as the JMX agent. The MBean server manages the JMX resources. These resources are called MBeans, that is, managed beans, which are used for instrumentation; MBeans provide data or perform operations. In order to interact with an MBean, it has to be registered with the MBean server first. This is typically done in the code, or if you are using Spring, you can configure Spring to do it for you. We'll see how to do this in the next section.

Finally, MBeans are accessed through a remote manager. Java2SE ships with jconsole and jvisualvm to manage MBeans and monitor VM resources. The jvisualvm manager has a very nice graphic interface, but it doesn't offer managing MBeans out of the box; for that, you would need to install the jconsole plugin.)

Each runtime environment must start the MBean server before MBeans can be registered and managed. For example, command-line applications need to be started like this:

java -Dcom.sun.management.jmxremote MyAppMain

Application servers, such as Tomcat, JBoss, and others, need to be instructed through their configuration to start an MBean server. For example, if you are using Tomcat, you need to modify the startup script, typically called catalina.sh (in Unix) or catalina.bat (in Windows), and add the following options:

CATALINA_OPTS=-Dcom.sun.management.jmxremote 
  -Dcom.sun.management.jmxremote.port=6666 
  -Dcom.sun.management.jmxremote.ssl=false 
  -Dcom.sun.management.jmxremote.authenticate=false

You can change the port, enable SSL, and force the client to authenticate. It's highly recommended that you secure your application using SSL and enable authentication. Refer to your application server documentation for further information.

Now that we have a good introduction to JMX, let's see how we can use it to collect Hibernate statistics and perform certain operations such as enabling or disabling Hibernate statistics or resetting statistical data.

Using JMX with Hibernate

In this section, first we will see how to create an MBean to report statistical data and also perform certain operations such as enabling/disabling statistics or resetting the data. We'll also see how to register the MBean with the platform MBean registration server or use Spring to perform that function for you.

For this example, we will use a web application that is deployed on Tomcat, version 7. If you haven't already enabled Tomcat to start the MBean server, refer to the previous section to see how to do that.

If you have never worked with JMX, we recommend that you look at Java tutorials. But you may also learn by walking through our scenario, as it goes nearly step by step.

The first thing we need to do is to create the JMX managed bean. You will need an interface and a concrete implementation. In this case, our interface is as shown here:

 package com.packt.hibernate.jmx;

 public interface HibernateStatsMBean {
  public void resetStatistics();
  public void enableStatistic();
  public void disableStatistic();
  public boolean isStatisticsEnabled();
  public String getStartTime();
  public long getCounnectionCount();
  public long getSessionOpenCount();
  public long getSessionCloseCount();
  public long getEntityLoadCount();
  public long getEntityFetchCount();
  public long getEntityDeleteCount();	
}

The interface defines three operations: reset, enable, and disable statistics. It also defines attributes that can be fetched, such as the session factory start time and various counts. Now, let's take a look at the implementation:

public class HibernateStats implements HibernateStatsMBean {

  private SessionFactory sessionFactory;
  
  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }
  
  @Override
  public String getStartTime() {
    if (sessionFactory == null) {
      return "error! session factory is null!";
    }

    Statistics stats = sessionFactory.getStatistics();
    Date startTime = new Date(stats.getStartTime());
    return startTime.toString();
  }
    
  @Override
  public void resetStatistics() {
    if (sessionFactory == null) {
      return;
    }
    
    sessionFactory.getStatistics().clear();
  }

  …
  …
  …
}

This sample code doesn't include all the methods that are required to be implemented, but you can easily guess what they look like. You should note that this class has access to the SessionFactory instance. This is either injected by Spring (in my case) because Spring is managing the session factory, or, if you are managing the session factory, you will have to inject it before registering and using this managed bean.

In this case, the session factory is being managed by Spring. It is therefore better to allow Spring to declare the MBean as a Spring bean, so it can perform its own bean wiring. So, in your Spring root application context, add the following lines:

<bean id="hibernateStats"
   class="com.packt.hibernate.jmx.HibernateStats">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

Furthermore, to manage the registration of the MBean with Tomcat's MBean server, we can use a separate class. I chose to implement a Singleton and maintain the state of the registration, so the managed JMX bean is not registered more than once. Spring has a clean JMX support. Later, I can show you how to use Spring to manage MBean registration, so you don't need a class such as this. But it's good to know how this task is performed in case you don't use Spring:

public class MBeanManager {
  private HibernateStats hibernateStats;
  private ObjectName objectName;

  private static MBeanManager manager = new MBeanManager();

  private MBeanManager() {
  }

  public static MBeanManager instance() {
    return manager;
  }

  public synchronized void initialize(HibernateStats hibernateStatsBean) {
    if (hibernateStats != null) {
      return;
    }
    this.hibernateStats = hibernateStatsBean;

    final MBeanServer mbeanServer = ManagementFactory
      .getPlatformMBeanServer();
    try {
      objectName = new ObjectName("com.packt.hibernate.jmx:type=hibernateStats");
      mbeanServer.registerMBean(hibernateStats, objectName);
    } catch (final Exception e) {
      e.printStackTrace();
    }
  }

  public synchronized void destroy() {
    final MBeanServer mbeanServer = ManagementFactory
      .getPlatformMBeanServer();
    try {
      mbeanServer.unregisterMBean(objectName);
    } catch (final Exception e) {
      e.printStackTrace();
    }
    finally {
      hibernateStats = null;
    }
  }
}

Finally, we need to invoke the MBean manager at start up time. You can use a context listener to achieve this, as shown here:

@WebListener
public class ContextListener implements ServletContextListener {

  @Override
    public void contextInitialized(final ServletContextEvent sce) {    
    ApplicationContext appContext = WebApplicationContextUtils
    .getRequiredWebApplicationContext(sce.getServletContext());
    
    try {
        HibernateStats hibernateStats = (HibernateStats) appContext.getBean("hibernateStats");
        if (hibernateStats == null) {
          System.err.println("***** null bean!!!!");
          return;
        }
        MBeanManager.instance().initialize(hibernateStats);
    }
    catch (Exception e) {
      e.printStackTrace();
      }
    }

  @Override
  public void contextDestroyed(ServletContextEvent arg0) {
      MBeanManager.instance().destroy();    	
  }
}

Note that this listener is called when Spring's web application context is started. This is needed because the HibernateStats JMX bean is being declared as a Spring bean because it needs access to the Hibernate session factory, which is also managed by Spring.

You are done!

All you need to do is redeploy your web application and launch jconsole from the command line, and point it to your server:

$ which jconsole
/usr/bin/jconsole
$ jconsole

As you can see in the screenshot, jconsole recognizes the local processes that are currently running. Obviously, we need to connect to the Tomcat process, namely the process called org.apache.catalina.startup.Bootstrap. If jconsole warns about the SSL connection, you can just instruct it to use an insecure connection:

Using JMX with Hibernate

Once jconsole is running, you can click on the MBeans tab and find yours, and click on Attributes, as shown in the following screenshot:

Using JMX with Hibernate

If you click on the Operations tab, you can see that there are buttons for reset, enable, and disable operations corresponding to the methods you implemented.

Using JMX with Hibernate

The previous demonstration used Spring to implement the solution, but again, that was only because the Hibernate session factory was being managed by Spring. The purpose of the demonstration was to introduce the components that you need to implement the solution:

  • The MBean interface to define the service contract
  • The MBean implementation to use SessionFactory to expose statistics, metrics, and operations
  • The MBean manager class to register the managed bean with the platform's MBean server
  • The servlet context listener to trigger the registration

So, in reality, you can do this without Spring; the only catch is that you have to safely wire the session factory object into your MBean implementation. One way of doing this is by binding your session factory to JNDI, and then simply doing a context look up for the bound object in your MBean.

However, if you do in fact use Spring, you can eliminate the last two steps mentioned here, meaning that you let Spring add your MBean to the registry server, and you don't need a servlet context listener because, in this case, Spring is the context listener. All you need to do is add the following lines to the Spring configuration file:

<bean id="hibernateStats"
  class="com.packt.hibernate.jmx.HibernateStats">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="exporter"
   class="org.springframework.jmx.export.MBeanExporter"
  lazy-init="false">
  <property name="beans">
    <map>
      <entry key="bean:name=hibernateStatistics" 
        value-ref="hibernateStats" />
    </map>
  </property>
</bean>

The first bean declaration, as you have already seen, defines an ID for the MBean and wires the sessionFactory bean. The second bean is Spring's bean to export the given MBeans and register them with the platform's JMX server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.47.208