Как загрузить коллекцию LAZY-ассоциации для уже найденной сущности - PullRequest
1 голос
/ 11 июля 2020

Рассмотрим следующее:

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

Приведенный выше код выдаст org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.app.Car.wheels.

Мой вопрос в том, как реализовать метод loadWheelsForCar(Car c) без необходимости снова искать машину.

Другими словами, как SELECT * FROM WHEELS WHERE owner_car_id = car.id и добавить результат в коллекцию. Я, вероятно, могу сделать это вручную, но это единственный способ go?

Я знаю, что LazyInitializationException выдается, когда нет активного сеанса (не @Transactional вызывает создание новенький?). Я пытался:

@Transactional
public void loadWheelsForCar(Car c) {
    c.getWheels().size(); // will load wheels
}

, но возникло исключение.

В случае проблемы XY , причина, по которой я этого не делаю (CarService):

@Transactional
public Optional<Car> getByIdWithWheels(int carId) {
    Optional<Car> possibleCar = carRepository.findById(carId);
    possibleCar.ifPresent(c -> c.getWheels().size());
    return possibleCar;
}

потому что родительский объект (Car entity) в основном приложении имеет несколько ассоциаций @OneToMany, а некоторые из них также имеют вложенные. Если я буду следовать этому подходу, у меня будет несколько методов @Transactional, таких как getCarByIdWithWheels, getCarByIdWithSeats, getCarByIdWithSeatsAndWheels и т.д. c. Но я хочу иметь возможность делать что-то вроде:

Car c = carService.getById(1);
carService.loadWheelsForCar(c);
carService.loadSeatsForCar(c);

Я пробовал кое-что, найденное в сети, но каждое решение, которое я нашел, было «повторной загрузкой» объекта «Car».

1 Ответ

1 голос
/ 11 июля 2020

Я знаю, что LazyInitializationException генерируется, когда нет активного сеанса (разве @Transactional не вызывает создание нового?)

Hibernate создает новый сеанс. Но этот сеанс является fre sh, и он не знает вашего Car c, поскольку этот автомобиль не извлекается, не сохраняется или не обновляется в этом новом сеансе, потому что вы получили этот автомобиль в другом сеансе.

  • Вы знаете, что ваша машина, которую вы проезжаете, точно такая же, как и машина в базе данных, и между ними нет обновлений. К сожалению, в спящем режиме нет API, чтобы сказать, что это автомобиль в точности так, как он находится в базе данных, не проверяйте его с его базой данных и просто верьте вам. Не существует api типа session.attach(car).

  • Так что самое близкое, что вы можете сделать, это session.merge(car). Hibernate выдаст команду select, чтобы проверить, совпадают ли проезжаемый автомобиль и автомобиль в базе данных, и, поскольку он совпадает, он не будет выдавать обновление. В рамках этого процесса также были бы загружены колеса выбора.

    @Autowired
    EntityManager em;

    @Transactional
    public Car loadWheelsForCar(Car car){
        Car merged = em.merge(car);
        merged.getWheels().size();
        return merged;
    }
  • Вы увидите, что вышеуказанный метод не вызывает никаких обновлений, а только один запрос выбора, аналогичный приведенному ниже
    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=?
...