Postanowiłem zmniejszyć obciążenie bazy danych. Za każdym razem jak szło zapytanie do bazy (np chciałem pobrać wszystkie choroby – Disease.class) to JPA puszczał też zapytanie dotyczące innych typów nieprostych – np listy leków List, które są przypisane do tej choroby. Działo się tak bo w deklaracji encji ustawione było
@ManyToMany(fetch = FetchType.EAGER) private List<Medicament> medicaments;
ale dzięki temu jak przekazałem obiekt do widoku do jsp to mogłem w łatwy sposób dostać się do leków przypisanych do choroby
<c:forEach items="${disease.medicaments}" var="medicament"> ${medicament.name} </c:forEach>
W momencie jak chcę wyświetlić choroby i nic więcej to nie ma potrzeby puszczać tylu zapytać do bazy. Więc zmieniłem FetchType na LAZY. Spowodowało to, że JPA puszcza zapytanie dotyczące obiektu który jest w zapytaniu.
JPA niestety uniemożliwia zmianę FetchType w trakcie działania, np encja ma ustawione LAZY ale puszczam zapytanie z EAGER – przynajmnije nie mogłem znaleźć informacji na ten temat.
Stwierdziłem więc, że w JPA pobiorę dwa obiekty i wrzucę je do tablicy, a tę tablicę do listy
@Query("select d, m from Disease d LEFT OUTER JOIN d.medicaments m") List<Object[]> findWithMedicaments();
Poszło jedno zapytanie do bazy! Teraz mogę wyciągnąć z tablicy obiekty Dosease i Medicament
List<Object[]> diseasesObjects = diseaseRepository.findWithMedicaments(); for (Object objects[] : diseasesObjects) { Disease disease = (Disease) objects[0]; if(objects[1] != null) { Medicament medicament = (Medicament) objects[1]; disease.addMedicament(medicament); } }
ale niestety rzuca błedem:
Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: pl.tomo.entity.Disease.medicaments, could not initialize proxy - no Session
Pomyślałem, że utworzę drugą listę w Disease
@ManyToMany(fetch = FetchType.LAZY) private List<Medicament> medicaments; //pierwsza lista @Transient private List<Medicament> medicaments2; //druga lista
i zmienię metodę addMedicament żeby wrzucała Disease do listy która jest Transient(nie jest w bazie danych). I to działa!
Następnie pomyślałem, że na pewno musi istnieć jakiś sposób żeby puścić zapytanie i dostać powiązane obiekty, mimo ustawienia FetchType.LAZY. Z pomocą przyszedł stackoverflow
Dodałem beana:
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">true</prop> </props> </property> <property name="packagesToScan" value="pl.tomo.entity" /> </bean>
Wstrzyknąłem go do serwisu
@Service public class DiseaseService { @Autowired private SessionFactory sessionFactory; public void test(){ Session openSession = sessionFactory.openSession(); Query query = openSession.createQuery("Select d from Disease d"); List<Disease> list = query.list(); for (Disease disease : list) { Hibernate.initialize(disease.getMedicaments()); } openSession.close(); }
List list = query.list() puszcza Hibernate: select disease…
Hibernate.initialize(disease.getMedicaments()) puszcza dla każdej pozycji z listy Hibernate: select medicament…