Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve LazyInitializationException in Spring Data JPA?

Tags:

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.

like image 780
stevecross Avatar asked Oct 22 '14 12:10

stevecross


2 Answers

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()).

like image 146
Predrag Maric Avatar answered Sep 30 '22 07:09

Predrag Maric


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:

  • using a specific query that reads the entity
  • or by accessing the relation within business code (additional query for each relation).

Both approaches are far from perfect, JPA 2.1 entity graphs are a better solution for it:

  • http://www.thoughts-on-java.org/jpa-21-entity-graph-part-1-named-entity/
  • http://www.thoughts-on-java.org/jpa-21-entity-graph-part-2-define/
like image 42
Pleymor Avatar answered Sep 30 '22 06:09

Pleymor