Mapowanie bazy w JPA w praktyce

0

Witajcie,
Mam do was stricte teoretyczne pytanie. Korzystam z JPA. Mam napisane proste forum (struktura relacji tabeli w załączniku). Mój problem polega na tym, że gdy na stronie głównej chcę wczytać wszystkie sekcje kategori, orm automatycznie wczytuje wszystkie kategorie (bo mam tak zdefiniowaną relacje), następnie wszystkie tematy, potem wszystkie posty z tematów, a następnie wszystkich userów, którzy dodali jakikolwiek post. Jak zapewne nie trudno się domyśleć, jest to znaczna część bazy. Większość tych danych nie jest mi potrzebna na stronie głównej. Dlatego wychodzę do was z pytaniem. Jak ograniczyć ilość wczytywanych danych przez orma, nie rezygnując z relacji? Czytałem o transakcjach i o LAZY initialization, ale nie mam pomysłu jak użyć tego w praktyce. Powinienem wykonać metodę z JpaRepository z poziomu DAO i jeszcze przed zakończeniem wykonywania metody, która ma adnotację @Transactional powołać się na pole, które będzie potrzebne do wyników zwracanych przez tą metodę? Prosiłbym o jakieś porady lub gotowe projekty na githubie, na których mógłbym się wzorować :)

Pozdrawiam

1

Oznacz po prostu w klasach encyjnych odpowiednie pola przez FetchType na Lazy. Ale pamiętaj że tam gdzie jednak chcesz wyciągać pozostałe dane musisz dodawać fetch joiny do zapytań, bo inaczej nie będzie ci pobierać tych powiązanych pól.

0

Na pewno będzie łatwiej jeśli załączysz również reprezentację tej struktury w Javie.

Jeśli chodzi o użycie LAZY, to przed joinem używasz odpowiedniej adnotacji, np:

@Entity
@Table(name = "post")
public class Post implements Serializable{
	@Id
 	private String id;
 	private String title;
 	private String text;

	@OneToMany(fetch=FetchType.LAZY, mappedBy="comment")
 	private List<Comment> comments;

 // setters and getters
}
0

Mam już zmapowaną bazę do obiektów. @Shalom fetch joiny mam robić po prostu w taki sposób, że przed zamknięciem sesji muszę wykonać metodę? Taką którą mógłbym pobrać ten obiekt do którego odnosi się relacja?

0

Nie, to jest idiotyczny pomysł i spowoduje n+1 selectów. Masz w zapytaniu ktorym wyciągasz dane z bazy dodać fetch join. Ale póki nie pokażesz jakiegokolwiek kodu to wróżymy z fusów...

1

Jeszcze w ramach ciekawostki:
Możesz pobrać tylko te dane, które Cię interesują, poprzez odpowiednio skonstruowane zapytanie, które wykorzystuje konstruktor. W takim przypadku, Twoje encja musi mieć zdefiniowany konstruktor, który przyjmuje te parametry, które chcesz pobrać, np:

	public class Post implements Serializable{
	@Id
 	private String id;
 	private String title;
 	private String text;

	@OneToMany(fetch=FetchType.EAGER, mappedBy="comment")
 	private List<Comment> comments;

	public Post(){}

	public Post(String title, String text){
		//...
	}

 	// setters and getters
}

Następnie piszesz zapytanie w postaci:

	SELECT NEW pelna.nazwa.paczki.Post(p.title,p.text)
	FROM Post p

Zwróć uwagę na 'NEW' oraz fakt, że musi być podana pełna nazwa paczki, w której znajduje się encja.

To zapytanie zwróci tylko title i text, nie zależnie od sposoby pobrania danych z łączonych tabel (EAGER, czy LAZY). Co jest w tym fajne, to że nie musisz się później martwić o joiny jak potrzebujesz wszystkich danych i dostajesz prawidłową strukturę obiektu, nie ma więc problemu ze zwróceniem np. JSONa.

W takiej encji możesz zdefiniować kilka zapytań (@NamedQuery), które później tylko wywołujesz w kodzie.

0

No to np. klasy:

@Entity
@Table(name = "topics")
public class Topic {

    @Id
    @GeneratedValue
    @Column(name = "topic_id")
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "category_id")
    private Category parent;

    @Column(nullable = false, length = 64)
    private String name;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "topic_id")
    private List<Post> posts;

    /* Getters and Setters */

}
@Entity
@Table(name = "posts")
public class Post {

    @Id
    @GeneratedValue
    @Column(name = "post_id")
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "topic_id")
    private Topic parent;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User creator;

    @Column(nullable = true)
    private Date date;

    @Column(length = 2^16)
    private String message;

    /* Getters and Setters */

}
@Entity
@Table(name = "categories")
public class Category {

    @Id
    @GeneratedValue
    @Column(name = "category_id")
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "section_id")
    private CategorySection parent;

    @Column(nullable = false, length = 64)
    private String name;

    @Column(nullable = false, length = 128)
    private String description;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "category_id")
    private List<Topic> topics;

    /* Getters and Setters */

}

Chodzi o to? http://jaceklaskowski.pl/wiki/Testowanie_z%C5%82%C4%85cze%C5%84_FETCH_JOIN_w_JPA

1

Tak, mniej więcej o to. Chodzi o to żebyś nie miał wszędzie fetch = FetchType.EAGER i żebyś miał kilka osobnych metod do pobierania tych samych obiektów, tylko że jedna metoda ma fetch join (i wyciąga jakieś powiązania z bazy) a inna nie ma.

0

Ok, rozumiem. Odezwę się jak przeczytam artykuł i zmodyfikuję kod który mam :)

0

Przeczytałem wspomniany wyżej artykuł. W kodzie testowym występuje takie coś:

        // Ilość kont przypisanych do Jacka to ilość_kont^ilość_użytkowników. Dlaczego?
        int iloscKont = 3, iloscOsob = 2;
        assert osoby.get(0).getKonta().size() == Math.pow(iloscKont, iloscOsob);

Znalazłem również inny artykuł http://piotrnowicki.com/2012/10/fetch-join-is-still-a-join/ Jest w nim napisane:

It’s very common problem with JOINs where each table from the left hand side is connected with more than one related rows in the right hand side table

Po przeczytaniu dalszej części artykułu, możemy zobaczyć że wzór ilość_kont^ilość_użytkowników jest błędny. Zamiast tego powinno być ilość_kont*ilość_użytkowników.

Zaimplementowałem to w swój kod i oczywiście mam problem z dublującymi wynikami. Zależy mi na zachowaniu ich kolejności więc nie mogę się zastosować do przedstawionej tutaj porady z setem http://stackoverflow.com/questions/18753245/one-to-many-relationship-gets-duplicate-objects-whithout-using-distinct-why Po użyciu DISTINCT wyniki nie dublują się, ale zastanawiam się czy takie rozwiązanie jest w porządku od strony technicznej.

No i pojawia się następne pytanie :)
Jeżeli chciałbym pokazywać najnowszy temat dla każdej z kategori to muszę robić kilka SELECT'ów, czy można to jakoś sprytnie zrobić?

0

No i co zrobić jeżeli potrzebuję tylko jedną stronę wyników z FETCH JOIN?

0

Chyba mam pewien pomysł :). Te wyniki dublują się po to, żebym mógł zrobić na nich setMaxResults w JPA?

0

SELECT c FROM CategorySection c JOIN FETCH c.categories bez duplikatów SELECT DISTINCT c FROM CategorySection c JOIN FETCH c.categories

Pamiętaj, że ten DISTINCT nie jest na poziomie SQL a HQL. Oznacza to, że nie jest robiony na bazie, ale w pamięci, na pobranych danych. Wg. mnie paginacja wyników będzie działała zupełnie losowo.

Nie rozumiem do końca, jakie masz tam duplikaty - zapytanie źle łączy dane? Pojawiają się duplikaty po dodaniu nowych danych? Jak masz opisaną relację CategorySelections - Category?

Napisz sobie w SQL zapytanie, żeby pobrać te dane, które chcesz i sprawdź, czy to które masz w JPA faktycznie jest tym co chcesz.

0

Powiedzmy że z DISTINCT sobie poradziłem. Zastanawia mnie jak zrobić order i limit na wynikach fetch join

1 użytkowników online, w tym zalogowanych: 0, gości: 1