I have to classes that have a one-to-many-relation. When I try to access the lazily loaded collection I get the LazyInitializationException
.
I searching the web
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:
You should enable Spring transaction manager by adding @EnableTransactionManagement
annotation to your context configuration class.
Since both services have @Transactional
annotation and default value
property of it is TxType.Required
, current transaction will be shared among the services, provided that transaction manager is on. Thus a session should be available, and you won't be getting LazyInitializationException
.
You have 2 options.
Option 1 : As mentioned by BetaRide, use the EAGER
fetching strategy
Option 2 : After getting the user
from database using hibernate, add the below line in of code to load the collection elements:
Hibernate.initialize(user.getCreatedJobs())
This tells hibernate to initialize the collection elements
Change
@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.
For those who have not the possibility to use JPA 2.1 but want to keep the possibility to return a entity in their controller (and not a String/JsonNode/byte[]/void with write in response):
there is still the possibility to build a DTO in the 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);
}
}
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())
.