Lazy Loading, new connections, and Java deadlocks

119 views
Skip to first unread message

Mike Fotiou

unread,
Aug 30, 2012, 7:33:54 PM8/30/12
to mybati...@googlegroups.com
Hi Guys,

  Lazy Loading is a great feature but we've run into a problem - the lazy loading retrieves it's own connection, even when another connection and transaction is open.  This leads to locks between the same code in the same thread.

  We use Mybatis Guice, but the principle would be the same without it.

  E.G.

  - do some work - update, delete, etc... in a transaction - connection id = 100
  - iterate through a collection of an object - lazy loading kicks in and grabs it's own connection, id  = 101
 - select is made on a row that was updated earlier on - since 101 knows nothing about the transaction that 100 is part of, it waits on 101 which cannot proceed

Is there any way to hook into MyBatis to force the lazy loader to use an existing connection, one provided by the session manager, for example?

Thanks,

Mike

Eduardo Macarron

unread,
Aug 31, 2012, 4:08:41 AM8/31/12
to mybati...@googlegroups.com
That should work... Lazy objects are created with a reference to current executor (session) so if that session is not still closed the executor is reused (or should be...).

And If fact... I am afraid that behaviour is wrong (or should be reviewed) because the session is not thread safe so that will work fine if lazy loads are fired from the same thread but may not work if that objects are accessed from other threads and for example the original theread closes the session meanwhile....

Mike Fotiou

unread,
Aug 31, 2012, 5:17:33 PM8/31/12
to mybati...@googlegroups.com
Hi Eduardo,

Thanks for responding to my post.

Yes, this is all being done in one thread (servlet request).

I've dug through the code, and I'll admit that I'm a little bit lost.

What I can see is that a proxy object creates a brand new simple executor in the newExecutor() method in the ResultLoader class.  The transaction always has a null connection object, so it retrieves a new connection on demand from the datasource.

In contrast, the main (top) level mapper method gets it's session from the SqlSessionInterceptor.

In my case, it appears that the line:

final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();

returns null and a new session is instead created a few lines later:

final SqlSession autoSqlSession = openSession();

How can these two methods know about each other's connections & transactions?

Note also that I'm using the Guice MyBatis plugin - I would have assumed that the session would be injected at some point when the mapper itself is injected into the using object.

Your thoughts would be greatly appreciated.

Thanks,

Mike

Mike Fotiou

unread,
Sep 1, 2012, 12:33:32 PM9/1/12
to mybati...@googlegroups.com
After reading the code a little bit more, I understand what is happening.

The object graph is loaded early on in the request - the associations and collections are lazy load enabled via the CGLIB proxies.  They have a connection related to the original, top-level query, which is not a "managed" session/connection.  Later on, the object collections are used to assist in creating update and insert statements within an @Transactional annotated method, which automatically puts the session into managed mode, and thus is attached to the request (thread) via thread local storage.  There is now a problem as the insert/updates and any new select statements run within the scope of the managed session (and therefore transaction) while the lazy loaded properties use the original, unmanaged connection.

The solution was to manage the entire request up front by starting a global transaction.  In our case, this was to create a Struts2 Interceptor that does nothing, but does have the @Transactional annotation.  This actually helps with performance, since the session is now cached for all database requests in the thread, rather than constantly going out to the openSession() method and utimately to the datasource.

The key, really, is that the entire thread should fall within a managed session.  That will ensure that all database access within the thread uses the same connection and participates in the same transaction.

I think that the Guice plugin, with all of its magic, gave us the false sense that the connections where scoped to the servlet request, when, in fact, it is only the SqlSessionManager which is in Singleton scope.  It's still up to the developer to manage the actual session/connection himself.

If you can think of a better way to do this or some pitfalls that we might encounter, please feel free to comment!

Thanks,

Mike

Chris

unread,
Sep 3, 2012, 7:03:51 PM9/3/12
to mybati...@googlegroups.com
The first project I worked on rather naively set up the application to use lazy loading all over the place.  We didn't use caching...  Performance was a nightmare.  We were not able to implement a caching solution, so we basically had to rewrite a ton of data access code.  What happened was that a single user request ended up triggering dozens (and dozens) of separate requests against the database.  Even though connections were pooled by the application server, if you multiply the overhead of a single remote request times 100 (or way more) we had issues...

I'm not saying anything bad or good about lazy loading.  Just that, like with anything else, make sure you understand all the implications of your decisions :)  Good luck.

Chris



From: Mike Fotiou <mof...@gmail.com>
To: mybati...@googlegroups.com
Sent: Saturday, September 1, 2012 11:33 AM
Subject: Re: Lazy Loading, new connections, and Java deadlocks

Mike Fotiou

unread,
Sep 4, 2012, 7:36:44 PM9/4/12
to mybati...@googlegroups.com
Hi Chris,

Thanks for the comments.

I agree that aggressive lazy loading would not be the way to go.  Right now, we're set up to lazy load as required.  The object model is rich, and is a very intuitive way to program when the object graph is full featured, so we wanted to push for that.  But our performance sucked prior to lazy loading and we were either going to have to go with specific queries for every particular action or implement our own lazy load pattern.  Luckily, the application work flow is very segregated.  A sub-section of the top-level object (e.g. a template or an evaluation) is used in a part of the work flow but not another, so only those pertinent objects are actually triggered in the lazy load.  

So far so good, lazy loading has given us a huge performance increase over loading the entire graph on every request, but, as you said, it's not without it's pitfalls.  You really have to make sure that you don't iterate through all of the sub-collections and associations unless you really need to or if you do, go with an entirely custom-built query for that action/view/use case.

We cache lookups and such, but the problem with caching the top level objects is that an update to any parts of the object graph force the entire object to be kicked out of the cache, which would be happening all of the time.  It would be nice to say to MyBatis - cache this collection (as part of the top-level result set) and only remove it from the cache if an update occurs to one of the objects in the collection.  But the way MyBatis works, with sub-selects, it doesn't care about the cache, it will always run the full query.  It's the entire top level object and all of it's collections that are cached.

I'd love to hear more about people's architectures related to object models and data access.

Right now, the object graph is rich but we pass everything through to a service to load it and save it.  On another project, we had the objects save themselves.  It was a really compact way to represent everything (user.save, asset.update, etc... and the asset.update() would end up calling a collection that would in turn persist themselves).  That caused problems because we weren't able to serialize the objects since they contained connections and other unserializable stuff.  Even if those were made transient, it was a nightmare to re-create everything required coming out of the session or cache.  Add to that AOP which creates totally nonsensical class names (and short lived ones) and you're really writing yourself into a corner.

Thanks again,

Mike
Reply all
Reply to author
Forward
0 new messages