Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

Tuesday, August 18, 2009

Hunting bugs

The last several weeks was a bit nervous. Our customers reported several similar bugs and we had absolutely no idea of where they come from. Fortunately now the cause of bugs seems to be settled (though yet to be solved) thanks to extensive logging (the thing I talked about in the first blog post).

I will talk here using some domain specific terms like Driver entity & getStatus() operation but I think it should not make understanding harder (if you have different opinion, write a comment and I'll try to explain if something is unclear)

The bug pattern was (as we discovered it):
  • Driver performed closeOrder() operation which should have changed his status + status of some order. Driver must have been set FREE status, Order must have been set ARCHIVED status.
  • Concurrently driver's mobile client executed background request getStatus() (which is executed once in half a minute automatically). This should have set only LAST_POLLING flag on Driver entity
This resulted in a very strange combination. Driver remained BUSY while Order was successfully set to ARCHIVED status which means that system was put into inconsistent state. As we first thought getStatus() command must not have any relation to the fact that driver somehow remains in (or is put back to) BUSY status (because it only changes 1 unrelated attribute LAST_POLLING). But we were wrong because of 2 things:

The first one is more general one: transaction isolation level (READ COMMITTED) was insufficient for transactions performed by operations mentioned. This made possible race condition between 2 threads executing different transactions. As a result thread executing getStatus() was able to
  • See driver in BUSY status (because it managed to read driver's state before closeOrder() operation
  • Write driver's status (because it managed to commit after! closeOrder() committed
But now one can ask: how does operation which only should have written LAST_POLLING managed to overwrite driver's status? This is because of the second thing which is related to the default Hibernate settings:
By default Hibernate for any given entity executes updates involving not only fields changed during Hibernate session but all entity fields! This allows it to prepare update operations on application startup. But this also makes application more fragile to race conditions. So I want to warn everyone about this property of the default Hibernate's strategy. It can be changed by setting dynamicUpdates entity attribute to true (either in annotations or in xml descriptors)

Setting dynamicUpdates to true only lowers the chance of race condition between different threads but not eliminates it. So now we are working hard on making possible Hibernate optimistic locking for Driver entity. The main difficulty here is to make some common operation be able to recover from OptimisticLockException (recovery means be able to try the same sequence of actions again)

Tuesday, August 11, 2009

Order by in Hibernate named queries

Another annoying thing I discovered today with Hibernate is about putting dynamic Order By clause to named queries. You can not normally Order By column name because if you put into your query something like ' order by :columnName' and do something like setParameter(":columnName", "id") it won't work as id value will be put quoted into prepared statement. Even more the following query 'select * from Order o order by :column :direction' will result in error! Hibernate won't be able to parse this query.
On one of the forums I found 'nice' workaround suggesting put column numbers instead of column names, this seems like very inconvenient solution, also anyway this won't solve problem with sort direction.
If anyone reading this blog (is anyone here?! :)) ) knows better alternative - I'll be glad to hear. Currently marking named queries as 'useless' feature in our project.
Update: as suggested in comments there is a solution (quite a simple one I should admit) which is to wrap query string retrieved named query into another query call like that:

Query q = s.createQuery(s.getNamedQuery("query.without.order.by").
getQueryString()+ " order by " + sortByColumnName);

Thursday, August 6, 2009

Hibernate query filter vs load/get

ETaxi project uses Hibernate as JPA provider (which means nothing surprising :) ). We are using Hibernate Query Filters, one nuance of which I'm going to discuss here, so at first some short information about them

Query Filter comprises of filter definition declaring filter name and parameters , filter metainformation for an entity defining what SQL string will be appended to where clauses of queries for the entity, and code that enables filter by invoking

Filter filter = session.enableFilter("filterName") ;
filter.setParameter("key1", value1);
...
filter.setParameter("keyN", valueN);


The purpose of using them is to make our life a bit easier. Our application has multiple working areas each of which should be accessible only by corresponding customer. So customer specific entities are filtered by default, and we don't need to put customer id explicitly into the list of our DAO method parameters. For example, I mean method which should look like:

public interface DriverDao {
public List findAll(Customer customer);
}

Looks instead as:

public interface DriverDao {
public List findAll();
}

Thus when filters are off it is possible to retrieve all drivers, when on - only those belonging to current customer. The benefits we have from using filters are - shorter queries, less parameters for dao methods, it's harder to forget to include filtering by customer to some query (and have one customer see other customer drivers/users/orders etc, which means very angry customers)

But there is at least one annoying 'But', query filters don't apply to results of Session.get() and Session.load() methods (which try to locate objects in cache before querying database), so when you want filters to work you need to use a query finding entities by id instead of get/load methods - and now you have your session cache not working on simple entity find by id!

Today after analyzing one of the typical requests to our app I discovered that current user is queried 5 times during it! Sure this partially can be helped by redesigning user searches inside application, but in this way we loose one of the benefits of Hibernate (because we are trying to use the other one consistently)

Till recently I even have not understood why this is made so that load()/get() can not make use of filters, but it is now clear to: suppose you turned filter off and loaded some entity which normally should be filtered, than turned filter on and try to load() that entity by id. How can simple cache search know should particular entity be filtered or not? (considering that applied filter is only a piece of SQL that is appended to queries, probably containing parameters that are also filled when preparing JDBC statement)

So query filters turned to be one of those Hibernate features that should be paid for and for which is hard to predict in what way you will have to pay (if you don't have some experience using). I wish this post will help someone make more conscious decision about whether or not to use them