This week I’ve been fighting Hibernate again. That is, last week I should have implemented a quick job to export 30 million entities from a DB and to a new interchange format.
Instead the job blew up due to memoryproblems. Below you’ll find teh solution.
First we thought this was due to a Hibernate bug, so we upgraded Hibernate - and spent a day fixing the consequences (thank god, we had tests). This didn’t help.
The problem was that the Hibernate querycache didn’t evict anything.. The heap was filling up with org.hibernate.engine.query.QueryPlanCache objects.
Now we did what we should have done in the first place. Google’d the terms”hibernate batch jobs”. We got these gems:
The changes we did in the end were:
1. Remove the use of Spring autowired DAOs to ensure control over queries.
This is important both to ensure that the number of connections don’t spiral out of control AND to clear the Hibernate entity caches.
If you are using a Spring SessonFactory, you can get the session using the SessionFactoryUtils:
Session s = SessionFactoryUtils.getSession(sessionFactory, true);try {doExport(s);} finally {s.flush();s.clear();SessionFactoryUtils.closeSession(s);}
The s.flush() and s.clear() are essential. If not, Hibernate will cache the objects it has created for you forever.
2. Make sure you use parameterized queries.
The point here is that this query:
Query query = s.createQuery("SELECT C.* from Blog where C.id="+ id);
Will be cached separately for EACH time it is called. If you call it 100 000 times, the QueryPlanCache will contain 100 000 entries. The solution is this:
Query query = s.createQuery("SELECT C.* from Blog where C.id=:id");query.setInteger("id", id);
DUH!
3. Love thy jmap
Use Eclipse to analyze jmap heapdumps. It’s easy and rewarding. Never try to solve a problem you do not know you have.
4.Bonus camel tip
This does not do what you thought it would:
//.. in some camel route ....to("seda:myQueue").to("file://...")
With this you are essentially sending the message two places. I should have known :)