While I have been responsible for some - well, many - missteps when implementing a JPA (+Spring+Hibernate) layer for systems, this is one I'm pretty
TL;DR: Hibernate throws a "LazyInitializationException" within an @Transactional method, which turns out to be because I attempted to "pre-load" the caches during startup, only to have Lazy (Proxy) objects loaded and used during a subsequent (i.e. "Session closed") request. Removing the pre-loading or using an EntityGraph to eagerly fetch the data corrected it.
I had 2 tasks to work on this week:
- Finish the transition to Spring Data JPA
- Minimize the latency for clients requesting data
#1 was going well, but I was still seeing > 60s "Time To First Byte" (TTFB) (#2), and that kept creeping up while adding data. We're talking > 15,000 rows of multi-table (transformed) data being returned.
Fortunately, that high latency was only on the initial load. Subsequent loads used the cached versions, so I was hitting < 7 second loads for 15,000+ rows of data.
"So...", I think, "why not pre-load the data!" It makes sense, and I know there are potential pitfalls, but they should be fairly simple to work through if I search around the 'net.
Wow. Mrs. Foot, meet Mr. Mouth.
I add in the call to load the data at startup, and then make my request, ready to praise myself for being a genius.
"LazyInitializationException - no Session"
WTF?
I trace through the request, everything needing an @Transactional - and being called via a Spring Managed Component - is in place, and even the TransactionManager says the transaction is active right before accessing the proxied fields.
What. The. <Fill in favorite curse>?
I expected, and saw, the "proxy" class being used by the loaded data. The methods extracting the fields are being called within the @Transactional (using the getter's and setter's as required; not direct field access).
It started percolating in my head that perhaps data was being loaded from another thread. Yet, nothing else was running/processing at the time; this was the only thread loading and using data.
Well...I did have a code to load the data from the database after startup. But...it loaded...
Oh...F... It called the repository method, but never accessed any of the lazy loaded data, so...it cached the proxy object, closed the session, and started laughing knowing the problems that would happen (yeah, my code is a jerk).
Remove initialization code. Rerun test. No LazyInitializationException. We're back to the long initial load, but that can wait for after getting everything working.