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.
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.
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:
Once jconsole
is running, you can click on the MBeans tab and find yours, and click on Attributes, as shown in the following screenshot:
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.
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:
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.
3.21.98.207