I highly recommend reading them. The first post is directly about tips and tricks when working with Hibernate; and knowing what to do to optimize your queries. The author recommends that unit testing and metrics using System.currentTimeMillis() can help you get the information you need to start the optimization process right.
Hibernate 3 also ships with a complete statistics and metrics API that allows you to figure out *everything* that is happening under the covers. All you have to do is a.) enable staticstics for the session factory and b.) retrieve the statistics and use them. Getting them an enabling them is easy:
SessionFactory sessionFactory = getSessionFactoryForApplication();
Statistics stats = sessionFactory.getStatistics();
stats.setStatisticsEnabled(true);
The tricky part (or at least the part that requires the most attention) is figuring out what statistics are available, and what they really mean. There are a billion methods available on the top level, but here is a glimpse (note, all of these values are based off of when statistics were enabled):
// Number of connection requests. Note that this number represents
// the number of times Hibernate asked for a connection, and
// NOT the number of connections (which is determined by your
// pooling mechanism).
stats.getConnectCount();
// Number of flushes done on the session (either by client code or
// by hibernate).
stats.getFlushCount();
// The number of completed transactions (failed and successful).
stats.getTransactionCount();
// The number of transactions completed without failure
stats.getSuccessfulTransactionCount();
// The number of sessions your code has opened.
stats.getSessionOpenCount();
// The number of sessions your code has closed.
stats.getSessionCloseCount();
// All of the queries that have executed.
stats.getQueries();
// Total number of queries executed.
stats.getQueryExecutionCount();
// Time of the slowest query executed.
stats.getQueryExecutionMaxTime();
There are also a lot of values related to the retrieval of your objects and collections of objects (directly or via association):
// the number of collections fetched from the DB.
stats.getCollectionFetchCount();
// The number of collections loaded from the DB.
stats.getCollectionLoadCount();
// The number of collections that were rebuilt
stats.getCollectionRecreateCount();
// The number of collections that were 'deleted' batch.
stats.getCollectionRemoveCount();
// The number of collections that were updated batch.
stats.getCollectionUpdateCount();
// The number of your objects deleted.
stats.getEntityDeleteCount();
// The number of your objects fetched.
stats.getEntityFetchCount();
// The number of your objects actually loaded (fully populated).
stats.getEntityLoadCount();
// The number of your objects inserted.
stats.getEntityInsertCount();
// The number of your object updated.
stats.getEntityUpdateCount();
In addition to all of this, there is information about cache performance (stolen from Hibernate documentation):
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
That is a whole bunch of statistics on object loading - these statistics are a good way to get your head wrapped around the total throughput of your application, but for most optimization attempts, they are a bit high level.
If that weren't enough, you can also get statistics for a specific query:
QueryStatistics queryStats = stats.getQueryStatistics("from Sale sale");
// total hits on cache by this query.
queryStats.getCacheHitCount();
// total misses on cache by this query.
queryStats.getCacheMissCount();
// total number of objects put into cache by this query execution
queryStats.getCachePutCount();
// Number of times this query has been invoked
queryStats.getExecutionCount();
// average time for invoking this query.
queryStats.getExecutionAvgTime();
// maximum time incurred by query execution
queryStats.getExecutionMaxTime();
// minimum time incurred by query execution
queryStats.getExecutionMinTime();
// Number of rows returned over all invocations of this query
queryStats.getExecutionRowCount();
Just like a made-for-tv knife salesman - but wait, there's more! You can also get the statistics for a particular entity:
EntityStatistics entityStats = stats.getEntityStatistics("com.javalobby.tnt.hibernate.Sale"); // or Sale.class.getName();
// exactly the same as the global values, but for a single entity class.
entityStats.getFetchCount();
entityStats.getLoadCount();
entityStats.getInsertCount();
entityStats.getUpdateCount();
entityStats.getDeleteCount();
But wait, there's more! You can see all of the statistics for any second level cache by region:
SecondLevelCacheStatistics cacheStats = stats.getSecondLevelCacheStatistics("com.javalobby.tnt.hibernate.sale.cache");
cacheStats.getElementCountInMemory();
cacheStats.getElementCountOnDisk();
cacheStats.getEntries();
cacheStats.getHitCount();
cacheStats.getMissCount();
cacheStats.getPutCount();
cacheStats.getSizeInMemory();
There is even more. You can get statistics for a collection associated with a particular type (say, for instance, the coupons used for a sale):
CollectionStatistics collectionStats = stats.getCollectionStatistics("com.javalobby.tnt.hibernate.Sale.coupons");
collectionStats.getFetchCount();
collectionStats.getLoadCount();
collectionStats.getRecreateCount();
collectionStats.getRemoveCount();
collectionStats.getUpdateCount();
Let's Come for some Tricks
1- It all starts from mapping:
As a hibernate developer, you must have a good knowledge about the ".hbm" files even if you use a generation tool. Mapping is a critical section, as a wrong or a bad mapped object may result in an unwanted heavy behavior. Make sure you define relations between objects in the right way, the tags you write in mappings defines how hibernate will build queries to retrieve and save your objects.
Hint 1: When mapping a one-to-one relations, its very effective to use the "constrained=true" attribute if you are sure that there is a record matching the foreign key association, this way, hibernate will not attempt a lookup first before selecting the records, it will direclty go and pick up the record as it previously knows of its existence.
Hint 2: Revisit your fetching strategy for objects, the "fetch" attribute tells hibernate how to retrieve a certain relation, it has many options. You may need to tell hibernate to use a certain type of join for a specific object, or a sub-select for another, that depends on the situation.
2- Queries:
Your queries may be the source of the problem, you may override an association strategy defined in the mapping file and then wonder why it is going wrong!!. Using SQL, HQL, or Criteria API are all valid options for object retrieval. Criteria API just gets translated to HQL before execution, so, using HQL is slightly better in terms of performance. Using SQL is needed at some cases when HQL lacks a certain feature for a specific DBMS.
Using query caching may be effective in cases where the application does not write too much to the DB, as caching a result set of a query become invalid if any update, delete or insert takes place at the DB table, you may enable query caching by setting
hibernate.cache.use_query_cache true
and use the setCacheable(true) of the query class before execution of a query.
Hint 1: Suppose a users logs in to your website, simply, you will hit the database to get the user profile object to compare the password supplied by the user, using a default hibernate get operation will retrieve the whole object graph which may include references to other objects and as a result a bad looking join SQL statement may be produced, at the end we have a heavy object in memory only for getting a simple string value. In situations like this, you need to specify a certain retrieval approach that just gets only the information you need to be added to the object built and returned for later use. You need to see how to use hibernate to build objects only of certain information.
3- Locking:
Ok, you used a load testing tool and booom.... a locking problem occurs, you find that your application messed up with the records, so, you decided to move to pessimistic locking, but, oops, the application is now having deadlocks. This problem mostly arise from the DBMS, as mentioned before; hibernate manages to use the provided locking mechanisms of the DBMS, no memory locking takes place, a problem like this may need a DBA to rightly configure the DBMS to work smoothly with your application to handle locking situations. From the hibernate side, you need to revisit your queries which needs to handle locking scenarios in your application, you need to pay attention whith writing such queries as those queries may mess up things if not written with caution. (See how to use locking with hibernate)
hint 1: A problem might take place if you lock some object for update and then not doing anything with the hibernate transaction initiated the lock. This can be solved by making sure that under any circumstances the transaction will either commit or rollback, this way, the lock will be released, another solution, is by setting the locking timeout of the DBMS itself, so a lock will be released after some time either the transaction issues any further action or not.
4- Connection Pooling:
As mentioned before, using connection pooling is a very good practice if used properly, beside configuring a pool manager, you need to pay your code a visit to make sure that there is a thread some where holding a connection open, this situation mostly found in a missing or a miss-placed connection close call. Optimizing the usage of a connection pool may need the advice of the DBA.
Comments