JPA – N+1 Select Problem

Cześć.

Przed wami kolejny artykuł dotyczący standardu mapowania obiektowo-relacyjnego w Javie, którym jest JPA, a właściwie jego aktualnie najbardziej popularną implementacją,  jaką jest Hibernate.

W tym poście zajmę się częstym problemem zwanym „n+1 Select Problem”.

Co to jest n+1 Select Problem?

Wyobraźmy sobie sytuację,  w której mamy dwa byty – właściciel(Owner) oraz samochód(Car). Zakładamy, że właściciel może posiadać wiele samochodów, ale samochód może mieć tylko jednego właściciela (typowa relacja jeden do wielu).

@Entity
public class Owner {

	@Id
	@GeneratedValue
	private Long id;

	private String name;

	@OneToMany
	@JoinColumn(name = "owner_id")
	private Set<Car> cars;
}

@Entity
public class Car {

	@Id
	@GeneratedValue
	private Long id;

	private String name;
}

Chcemy znaleźć sposób, aby wyświetlić wszystkich właścicieli wraz z ich pojazdami:

List<Owner> ownerList = session.createQuery("FROM Owner o").list();

for(Owner owner : ownerList){
	... // show owner with cars
}

Jak widać, wpisaliśmy jedno zapytanie, ale czy na pewno Hibernate też wykonał tylko jedno zapytanie? Otóż nie! Hibernate „pod spodem” wykonał jedno zapytanie, żeby pobrać wszystkich właścicieli:

 SELECT * FROM Owner;

oraz po jednym zapytaniu dla każdego właściciela, aby pobrać jego listę samochodów:

SELECT * FROM Car WHERE Car.owner_id=?

Oznacza to, że nasza liczba zapytań to 1 (pobranie właściciela) + N (pobranie pojazdów każdego właściciela) i właśnie dlatego problem ten nazywa się „n+1 Select Problem”.
Przy małej ilości danych nie jest to utrudnienie, ale gdybyśmy mieli na bazie kilkaset albo kilka tysięcy właścicieli to taka ilość zapytań mogłaby „zabić” naszą aplikację.

Rozwiązanie:

Tak naprawdę mamy trzy rozwiązania tego problemu:
FETCH JOIN

List<Owner> ownerList = session.createQuery("FROM Owner owner JOIN FETCH owner.car Cars").list();

LEFT OUTER JOIN

SELECT * FROM Owner owner LEFT OUTER JOIN Car car ON car.owner_id = owner.id

Criteria query

Criteria criteria = session.createCriteria(Owner.class);
criteria.setFetchMode("cars", FetchMode.EAGER);

Bardzo dziękuję za uwagę i pozdrawiam.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *