How to resolve LazyInitializationException in Spring JPA data?

I have one-to-many classes. When I try to access a lazily loaded collection, I get a LazyInitializationException . I have been searching the Internet for a while, and now I know that I am getting an exception because the session that was used to load the class containing the collection is closed. However, I did not find a solution (or at least did not understand them). I mainly 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> {} 

User service

 @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; } } } } } 

applications

 @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 often read that the session should be tied to the current thread, but I don’t know how to do this using Spring annotation-based configurations. Can someone tell me how to do this?

PS I want to use lazy loading, so downloading is not an option.

+11
source share
6 answers

Basically, you need to get lazy data while you are inside a transaction. If your service classes are @Transactional , then everything should be fine while you are in them. As soon as you exit the service class, if you try to get lazy collection, you will get this exception, which is in your main() method, the line System.out.println(random.getCreatedJobs()); .

Now it comes down to what needs to be returned to your service methods. If userService.getRandomUser() is expected to return a user with task initialization so that you can manipulate them, then this is a way of responsibility for receiving it. The easiest way to do this with Hibernate is to call Hibernate.initialize(user.getCreatedJobs()) .

+7
source

You have 2 options.

Option 1: As indicated by BetaRide, use the EAGER sampling EAGER

Option 2:. After receiving user from the database using sleep mode, add the following line of code to load the elements of the collection:

 Hibernate.initialize(user.getCreatedJobs()) 

This indicates hibernation to initialize collection items.

+2
source

Consider using JPA 2.1 , with Entity graphs :

Lazy loading has often been a problem with JPA 2.0. You should have defined FetchType.LAZY or FetchType.EAGER in the object and made sure that the relation is initialized in the transaction.

It can be done:

  • using a special request that reads an object
  • or by accessing relationships within the business code (additional request for each relationship).

Both approaches are far from perfect, JPA 2.1 entity graphs are the best solution for it :

+2
source

Edit

 @OneToMany(mappedBy = "creator") private Set<Job> createdJobs = new HashSet<>(); 

to

 @OneToMany(fetch = FetchType.EAGER, mappedBy = "creator") private Set<Job> createdJobs = new HashSet<>(); 

Or use Hibernate.initialize inside your service, which has the same effect.

+1
source

For those who are not able to use JPA 2.1, but want to keep the ability to return the object to their controller (and not String / JsonNode / byte [] / void with an entry in the response):

It is still possible to build a DTO in a transaction that will be returned by the controller.

 @RestController @RequestMapping(value = FooController.API, produces = MediaType.APPLICATION_JSON_VALUE) class FooController{ static final String API = "/api/foo"; private final FooService fooService; @Autowired FooController(FooService fooService) { this.fooService= fooService; } @RequestMapping(method = GET) @Transactional(readOnly = true) public FooResponseDto getFoo() { Foo foo = fooService.get(); return new FooResponseDto(foo); } } 
0
source

You must enable Spring Transaction Manager by adding the @EnableTransactionManagement annotation to your context configuration class.

Since both services have the @Transactional annotation and the default value property, this is TxType.Required , the current transaction will be shared with the services, provided that the transaction manager is TxType.Required . Therefore, the session should be accessible, and you are not a LazyInitializationException .

0
source

Source: https://habr.com/ru/post/977074/


All Articles