I've looked at a lot of other questions about LazyInitializationException and @Transactional, and haven't seen anything that seems to help our case.
I apologise for being less than methodical investigating it. It's not my code but it's fallen into my hands for the time being...
We have code that looks like this:
@Entity
class TaskSchedule {
// fields that make up a task schedule
Date nextRunTime;
}
@Entity
class Task {
@OneToMany(fetch = FetchType.LAZY)
List<TaskSchedule> schedules;
@Transactional
public void doTask() {
// run the task
for (TaskSchedule schedule : schedules) { /* CRASH HAPPENS HERE */
// set the schedule's next run time
}
}
}
@Component
public class TaskDao {
@Transactional(readOnly = true)
public List<Task> getScheduledTasks() {
Calendar calendar = Calendar.getInstance();
Criteria criteria = getSession().createCriteria(Task.class);
criteria.add(Restrictions.le("runTime", calendar.getTime()));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
List<Task> results = criteria.list();
return results;
}
}
public class MonitorJob {
TaskDao taskDao;
public void runScheduledTasks() {
// start a read only transaction
List<Task> tasks = taskDao.getScheduledTasks(); // query database
// finish the transaction
for (Task task : tasks) {
// start a transaction
task.doTask(); // populate lazy list
// finish transaction
}
}
}
We get a LazyInitializationException exception when the lazy list schedules is iterated over:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Task.schedules, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:365)
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:108)
at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:272)
at Task.doTask(Task.java:366)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:197)
at Task_$$_javassist_43.doTask(Task_$$_javassist_43.java)
at MonitorJob.runScheduledTasks(MonitorJob.java:106)
at sun.reflect.GeneratedMethodAccessor68.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:273)
at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:299)
at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:111)
at org.quartz.core.JobRunShell.run(JobRunShell.java:203)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:520)
As far as I can tell, after the @Transactional method doTask is called for the first item in tasks, a whole lot of objects, including all the other lazy schedules lists for the remaining items in tasks, are removed from the session by having unsetSession called on them. This means that subsequent iterations over tasks crash because their lazy lists no longer have an associated session.
I haven't actually been working on this bit of code, but it has started failing recently (after our DB was reloaded). I can't see why it would have worked in the first place, if the objects get removed from the session.