Relacja Many-To-Many Jpa, w jaki sposób pobrać rekordy?

0

Posiadam następujące Enity (w uproszczeniu...):

public class Athlete {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column
    private Long id;

    @ManyToMany
    @JoinTable(name = "athlete_trainer",
            joinColumns = @JoinColumn(name = "athlete_id"),
            inverseJoinColumns = @JoinColumn(name = "trainer_id")
    )
    @Column
    private Set<Trainer> trainers;
} 
public class Trainer {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column
    private Long id;

    @ManyToMany(mappedBy = "trainers")
    @Column
    private Set<Athlete> athletes;

}

Oraz repozytorium np. AthleteRepository...

public interface AthleteRepository extends JpaRepository<Athlete, Long> {

    Optional<Athlete> getAthleteByUserId(Long userId);
}

Za nic w świecie nie potrafię pobrać wszystkich atletów danego id trenera lub na odwrót tj. wszystkich trenerów dla danego atlety.

Byłbym wdzięczny za pomoc.

0

Jeśli relacja jest zmapowana odpowiednio (nie analizowałem) to mając Optional<Athlete> mapujesz go na Set<Trainer> używając getTrainers na zawartości optionala.
Próbowałeś tak?

2

Uwaga, strzelam. Może jesteś już poza sesją? Z tego co pamietam relacja by default jest lazy, a jeśli nie założyłeś transakcji, to jest ona zakładana (co za tym idzie sesja) tylko na tej jednej metodzie repozytorium. W efekcie dostajesz tylko proxy w Secie, a nie prawdziwe encje. Strzelam. Wtedy rozwiązań jest kilka - eager fetching, fetch join, transakcja poziom wyżej, wywalenie many-to-many (zalecane).

0
kixe52 napisał(a):

Jeśli relacja jest zmapowana odpowiednio (nie analizowałem) to mając Optional<Athlete> mapujesz go na Set<Trainer> używając getTrainers na zawartości optionala.
Próbowałeś tak?

W ten sposób:

Optional<Trainer> findTrainer = trainerRepository.getTrainerById(trainerId);
Trainer trainer = findTrainer.get();
Set<Athlete> athletes = trainer.getAthletes();

udało się pobrać Set<Athlete> dzięki wielkie :)

Zastanawia mnie jeszcze tylko to czy mogę napisać jakoś to w interfejsie AthleteRepository...

coś w stylu:

Optional<Set<Athlete>> getAthletesByTrainersTrainerId(Long trainerId); 

IntelliJ podpowiada mi jedynie

Optional<Set<Athlete>> getAthletesByTrainers(Set<Trainer> trainers)

Więc może @Charles_Ray dobrze strzelił, chociaż dodałem i nic to nie zmieniło.

@ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "athlete_trainer",
            joinColumns = @JoinColumn(name = "athlete_id"),
            inverseJoinColumns = @JoinColumn(name = "trainer_id")
    )
    @Column
    private Set<Trainer> trainers;
2

Zostaw Repository w spokoju. Operacje na Optionalu rób w serwisie. Poza tym używaj twojOptional.map(tutaj operacje na zawartości optionala).orElseThrow(tutaj exception np, nie znalazlo trenera o takim id). Dając samo get() spotka Cie nullpointer gdy optional będzie pusty (nie znaleziono trenera o podanym id).

0

@Charles_Ray:

wywalenie many-to-many (zalecane).

A to czemu?

0
  1. Relacje powinny być w jedną stronę, jeśli się da. Zwykle są dodawane nadmiarowo i żaden scenariusz biznesowy tego nie wymaga.
  2. Klasa A wie o klasie B i odwrotnie - spięcie dwóch klas na wieki wieków, co uniemożliwia niezależną ewolucję domen i rozpięcie agregatów.
0

Dobra, załóżmy taki przypadek:
Ksiązka <---> Autor
Jedna ksiązka ma wiele autorów, a jeden autor może napisac wiele ksiązek. Fajnie byłoby móc znaleźć ksiązki po autorach i odwrotnie. Jak byś to zaimplementował? W bazie danych trzymał many-to-many z tabelką łączacą, a w JPA np. ksiązka miałaby Set<Author> authors, zas encja Author nic?
Oczywiście generalnie zgadzam się z tym co napisałeś, szczególnie z punktu widzenia drugiego(A zależy od B, B zalezy od A)

3

Było przerabiane 100 razy na forum. Model domenowy to co innego niż widok.

EDIT. Kilka kwestii. Dla mnie zawsze driverem jest biznes. Po pierwsze z tabelki łączącej może kiedyś powstać prawilna encja, np. jeśli to byliby użytkownicy i książki, to może warto zamodelować również „wypożyczenie”. Druga sprawa - może nie musisz tego pobierać przez JPA, które bardziej służy do modyfikowania danych (dirty checking, transakcje itp). Do pobierania i agregowania danych miałbym model zoptymalizowany pod odczyt i odpytywał to... sqlem. Co do modelowania - może wystarczyć id po jednej stronie i tak się rozpina agregaty, które powinno się pobierać tylko po id wg. biblii DDD. Reasumując - to, że jako programiści możemy coś zrobić, nie oznacza, że powinniśmy. Jak pomyśle o many-to-many w połączeniu jeszcze z kaskadami (np. cascade delete), to mi się słabo robi :)

1

Zawsze możesz napisać własne zapytanie w stylu:

@Query("SELECT t.athletes FROM Trainer t WHERE t.id = :trainerId")
public Set<Athlete> getAthletes (@Param("trainerId") Long trainerId);
1

@Charles_Ray: a tak z ciekawości korzystałes z JOOQ? Wydaje mi sie (choć jeszcze nie korzystałem) że to fajny pośrednik między JDBC a JPA - nie trzeba nacharować się jak przy Jdbc/JdbcTemplate a nie ma tyle magii co w JPA :)

0

Nie korzystałem, z tego co rozumiem, to jest mapper SQL <-> Java wystawiający fluent API. Wydaje się to spoko do querowania danych, ale musiałbym się pobawić. Nie ma tyle magii co w JPA, ale nie ma też dirty checkingu.

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