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.