I have two classes that have a one-to-many relation. When I try to access the lazily loaded collection I get the LazyInitializationException
. I have been searching the web for a while and now I know that I get the exception because the session that was used to load the class which holds the collection is closed. However, I did not find a solution (or at least I did not understand them). Basically I have these classes:
User
@Entity @Table(name = "user") public class User { @Id @GeneratedValue @Column(name = "id") private long id; @OneToMany(mappedBy = "creator") private Set<Job> createdJobs = new HashSet<>(); public long getId() { return id; } public void setId(final long id) { this.id = id; } public Set<Job> getCreatedJobs() { return createdJobs; } public void setCreatedJobs(final Set<Job> createdJobs) { this.createdJobs = createdJobs; } }
UserRepository
public interface UserRepository extends JpaRepository<User, Long> {}
UserService
@Service @Transactional public class UserService { @Autowired private UserRepository repository; boolean usersAvailable = false; public void addSomeUsers() { for (int i = 1; i < 101; i++) { final User user = new User(); repository.save(user); } usersAvailable = true; } public User getRandomUser() { final Random rand = new Random(); if (!usersAvailable) { addSomeUsers(); } return repository.findOne(rand.nextInt(100) + 1L); } public List<User> getAllUsers() { return repository.findAll(); } }
Job
@Entity @Table(name = "job") @Inheritance @DiscriminatorColumn(name = "job_type", discriminatorType = DiscriminatorType.STRING) public abstract class Job { @Id @GeneratedValue @Column(name = "id") private long id; @ManyToOne @JoinColumn(name = "user_id", nullable = false) private User creator; public long getId() { return id; } public void setId(final long id) { this.id = id; } public User getCreator() { return creator; } public void setCreator(final User creator) { this.creator = creator; } }
JobRepository
public interface JobRepository extends JpaRepository<Job, Long> {}
JobService
@Service @Transactional public class JobService { @Autowired private JobRepository repository; public void addJob(final Job job) { repository.save(job); } public List<Job> getJobs() { return repository.findAll(); } public void addJobsForUsers(final List<User> users) { final Random rand = new Random(); for (final User user : users) { for (int i = 0; i < 20; i++) { switch (rand.nextInt(2)) { case 0: addJob(new HelloWorldJob(user)); break; default: addJob(new GoodbyeWorldJob(user)); break; } } } } }
App
@Configuration @EnableAutoConfiguration @ComponentScan public class App { public static void main(final String[] args) { final ConfigurableApplicationContext context = SpringApplication.run(App.class); final UserService userService = context.getBean(UserService.class); final JobService jobService = context.getBean(JobService.class); userService.addSomeUsers(); // Generates some users and stores them in the db jobService.addJobsForUsers(userService.getAllUsers()); // Generates some jobs for the users final User random = userService.getRandomUser(); // Picks a random user System.out.println(random.getCreatedJobs()); } }
I have often read that the session has to be bound to the current thread, but I don't know how to do this with Spring's annotation based configurations. Can someone point me out how to do that?
P.S. I want to use lazy loading, thus eager loading is no option.
Basically, you need to fetch the lazy data while you are inside of a transaction. If your service classes are @Transactional
, then everything should be ok while you are in them. Once you get out of the service class, if you try to get
the lazy collection, you will get that exception, which is in your main()
method, line System.out.println(random.getCreatedJobs());
.
Now, it comes down to what your service methods need to return. If userService.getRandomUser()
is expected to return a user with jobs initialized so you can manipulate them, then it's that method's responsibility to fetch it. The simplest way to do it with Hibernate is by calling Hibernate.initialize(user.getCreatedJobs())
.
Consider using JPA 2.1, with Entity graphs:
Lazy loading was often an issue with JPA 2.0. You had to define at the entity FetchType.LAZY or FetchType.EAGER and make sure the relation gets initialized within the transaction.
This could be done by:
Both approaches are far from perfect, JPA 2.1 entity graphs are a better solution for it:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With