Is there a good reliable way to determine where a Hibernate query is getting it’s data from (i.e. sesison cache or second level cache or from the db)? If I configure Hibernate with the following properties:
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
I receive a ton of information about selects which are run (although I don’t know where they are being retrieved from). Since this is a Spring MVC application, and all of our requests are handled with Spring Controllers I created the following interceptor:
public class ProfilingInterceptor implements HandlerInterceptor {
@Autowired private SessionFactory sessionFactory;
private static final String STATS = "hibernateStats";
private static final String START_TIME = "startTime";
private static final Logger LOGGER = Logger.getLogger(ProfilingInterceptor.class);
@Override public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
request.getSession().setAttribute(START_TIME, System.currentTimeMillis());
request.getSession().setAttributes(STATS, new HibernateStatistics(sessionFactory.getStatistics());
return true;
}
@Override public boolean postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HibernateStatistics stats = (HibernateStatistics)
request.getSession().getAttribute("STATS");
stats.update(sessionFactory.getStatistics());
request.getSession().setAttribute(STATS, new HibernateStatistics(sessionFactory.getStatistics()));
LOGGER.debug(stats);
}
@Override public boolean postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long startTime = (Long) request.getSession().getAttribute(START_TIME);
long currentTime = System.currentTimeMillis();
request.getSession().setAttribute(START_TIME, null);
long totalTime = currentTime - startTime;
LOGGER.debug("URI: " request.getRequestURI() + " Method: "
+ request.getMethod() + " took " + totalTime + "ms.");
HibernateStatistics stats = (HibernateStatistics)
request.getSession().getAttribute(STATS);
stats.update(sessionFactory.getStatistics());
LOGGER.debug(stats);
}
}
Here is what the HibernateStatistics object looks like (it’s an inner class):
private static final class HibernateStatistics implements Serializable {
private static final long serialVersionUID = 1L;
private long queryExecutions = 0;
private long transactions = 0;
private long entityLoads = 0;
private long connects = 0;
private long time = 0;
private double secondLevelHits = 0;
private double secondLevelMisses = 0;
private double queryHits = 0;
private double queryMisses = 0;
public HibernateStatistics(Statistics stats) {
synchronized(stats) {
queryExecutions = -stats.getQueryExecutionCount();
transactions = -stats.getTransactionCount();
entityLoads = -stats.getEntityLoadCount();
connects = -stats.getConnectCount();
secondLevelHits = -stats.getSecondLevelCacheHitCount();
secondLevelMisses = -stats.getSecondLevelCacheMissCount();
queryHits = -stats.getQueryCacheHitCount();
queryMisses = -stats.getQueryCacheMissCount();
time = -System.currentTimeMillis();
}
}
public void update(Statistics stats) {
synchronized(stats) {
queryExecutions += stats.getQueryExecutionCount();
transactions += stats.getTransactionCount();
entityLoads += stats.getEntityLoadCount();
connects += stats.getConnectCount();
secondLevelHits += stats.getSecondLevelCacheHitCount();
secondLevelMisses += stats.getSecondLevelCacheMissCount();
queryHits += stats.getQueryCacheHitCount();
queryMisses += stats.getQueryCacheMissCount();
time += System.currentTimeMillis();
}
}
@Override
public String toString() {
return "Stats"
+ "[ queries=" + queryExecutions
+ ", xactions=" + transactions
+ ", loads=" + entityLoads
+ ", connects=" + connects
+ ", queryCacheHits=" + queryHits
+ ", secondLevelCacheHits=" + secondLevelHits
+ ", time=" + time + " ]";
}
}
I have been utilizing single threaded tests to find some of the situations where we might have the N+1 query problem in the code, but at this point our number of connections and transactions look good on all of our pages.
What I need now is a good way to determine how effective our second level and query caching is. The problem lies in that the HibernateStatistics are not thread safe. So when we try to multithread this I could see some weird numbers for these values. Is it as simple as gathering the stats and displaying them on a page for further analysis?
Why are passing around Statistics around in requests? Assuming you have configured the SessionFactory as singleton, sessionFactory.getStatistics() will always return the same Statistics object wherever and how many ever times you call it in the application. You don’t need to use the class HibernateStatistics for logging the statistics. You can just implement a static util method which could log the stats. Session factory will just accumulate the statistics from the moment it was initialized.
I the selects are in the log that means they are getting loaded from the db. If it is from the session/2nd level cache, queries are not logged by hibernate. The only reliable way check the performance of the cache is to monitor the hit/miss ratio in the cache stats. You can also enable stats at the entity level. Check here.