Consider the following:
@Entity @Table(name = "cars") public class Car { @Id private int id; private String name; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "ownerCar") private Set<Wheel> wheels = new HashSet<>(); private Car() { } public Car(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public Set<Wheel> getWheels() { return wheels; } }
@Entity @Table(name = "wheels") public class Wheel { @Id private int id; @ManyToOne private Car ownerCar; private Wheel() { } public Wheel(int id) { this.id = id; } public int getId() { return id; } public Car getOwnerCar() { return ownerCar; } public void setOwnerCar(Car ownerCar) { this.ownerCar = ownerCar; } }
@Override //CommandLineRunner public void run(String... args) throws Exception { Car car = new Car(1); car.setName("Ferrari"); Wheel wheel = new Wheel(1); wheel.setOwnerCar(car); car.getWheels().add(wheel); carService.saveCar(car); // Assume we have found the car already car = carService.getById(1).get(); // Load the wheels of this car carService.loadWheelsForCar(car); System.out.println(car.getWheels().size()); }
The code above will throw org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.app.Car.wheels
.
My question is how to implement loadWheelsForCar(Car c)
method without having to find the car again.
With other words, how to SELECT * FROM WHEELS WHERE owner_car_id = car.id
and add the result to the collection. I can probably do it manually, but is this the only way to go?
I am aware that LazyInitializationException
is thrown when there is no active session(Doesn’t @Transactional
cause the creation of a new one?). I have tried to:
@Transactional public void loadWheelsForCar(Car c) { c.getWheels().size(); // will load wheels }
but the exception is thrown.
In case of a XY problem, the reason I don’t do this (CarService
):
@Transactional public Optional<Car> getByIdWithWheels(int carId) { Optional<Car> possibleCar = carRepository.findById(carId); possibleCar.ifPresent(c -> c.getWheels().size()); return possibleCar; }
is because the parent entity (Car
entity) in the main application has multiple @OneToMany
associations and some them have nested ones as well. If i follow this approach I will end up with multiple @Transactional
methods like getCarByIdWithWheels
, getCarByIdWithSeats
, getCarByIdWithSeatsAndWheels
, etc. But what I want is to be able to do something like:
Car c = carService.getById(1); carService.loadWheelsForCar(c); carService.loadSeatsForCar(c);
I tried somethings found in web but every solution I found was “re-loading” the “Car
” entity.
Advertisement
Answer
I am aware that LazyInitializationException is thrown when there is no active session(Doesn’t @Transactional cause the creation of a new one?)
Hibernate does create a new session. But that session is fresh and it is not aware of your Car c
as that car is not fetched, saved or updated in that new session because you fetched that car in a different session.
You know that your car you are passing is exactly same as the car in the database and there is no update in between. Unfortunately there is no API in hibernate to tell that here is a car exactly as it is in database, don’t check it with it database and just believe you. There is no api like
session.attach(car)
.So the closest you can do is
session.merge(car)
. Hibernate will issue a select to check if the car you are passing and the car in the database are same, and since it is same, it will not issue an update. As part of that select wheels would have been loaded too.
@Autowired EntityManager em; @Transactional public Car loadWheelsForCar(Car car){ Car merged = em.merge(car); merged.getWheels().size(); return merged; }
- You will see the above method does not issue any update but just one select query similar to below
select car0_.id as id1_0_1_, car0_.name as name2_0_1_, wheels1_.owner_car_id as owner_ca3_1_3_, wheels1_.id as id1_1_3_, wheels1_.id as id1_1_0_, wheels1_.name as name2_1_0_, wheels1_.owner_car_id as owner_ca3_1_0_ from cars car0_ left outer join wheels wheels1_ on car0_.id=wheels1_.owner_car_id where car0_.id=?
- You can see the repo that does the above here. https://github.com/kavi-kanap/stack-overflow-62843378