Ten post będzie jedynie narzekaniem na niedoskonałość technologii, z którą pracuję nad projektem zgłoszonym do DSP.
Problem
W trakcie tworzenia Star Trek API muszę modelować relacje wiele do wielu w znacznej ilości. Często jest tak, że dwie encje mają między sobą kilka relacji wiele do wielu. Relacje te oznaczają oczywiście co innego. Np. mam filmy, a w nich relacje do ludzi pracujących nad Star Trekiem w różnych rolach. Oddzielnie są producenci, oddzielnie autorzy scenariusz, i tak dalej. W kodzie wygląda to na przykład tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class Movie extends PageAwareEntity implements PageAware { (...) @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "movies_screenplay_authors", joinColumns = @JoinColumn(name = "movie_id", nullable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "staff_id", nullable = false, updatable = false)) private Set screenplayAuthors = Sets.newHashSet(); @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "movies_story_authors", joinColumns = @JoinColumn(name = "movie_id", nullable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "staff_id", nullable = false, updatable = false)) private Set storyAuthors = Sets.newHashSet(); } |
Jak widać, relacja między encją Movie
i encją Staff
występuje więcej niż raz. Trzeba więc utworzyć dwie tabelki pośrednie, movies_screenplay_authors
i movies_story_authors
, dla obu tych relacji. Jeśli tabelek jest więcej, to trzeba utworzyć więcej relacji. W efekcie w w bazie mam tabelkę dla filmów i relacje filmów z innymi encjami:
movie
movies_characters
movies_directors
movies_performers
movies_producers
movies_screenplay_authors
movies_staff
movies_stand_in_performers
movies_story_authors
movies_stunt_performers
movies_writers
Z czasem coraz ciężej przeglądać mi bazę w SQL Developerze. Tabelek z danymi mam w tej chwili niespełna 20, a tabelek z relacjami ponad 50.
Rozwiązanie, które nie istnieje
To, czego brakuje w obecnej sytuacji, to możliwość użycia jednej tabelki pośredniej dla kilku relacji. Wydaje mi się, że jest to niemożliwe w JPA 2.1. Wyobrażam sobie, że od strony kodu wyglądać mogłoby to tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Movie extends PageAwareEntity implements PageAware { (...) @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "movies_staff", joinColumns = @JoinColumn(name = "movie_id", nullable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "staff_id", nullable = false, updatable = false), joinCriteria = @JoinCriteria(columnName = "staff_type", value = StaffType.SCREENPLAY_AUTHOR, enumType = EnumType.STRING)) private Set<Staff> screenplayAuthors = Sets.newHashSet(); @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "movies_staff", joinColumns = @JoinColumn(name = "movie_id", nullable = false, updatable = false), inverseJoinColumns = @JoinColumn(name = "staff_id", nullable = false, updatable = false), joinCriteria = @JoinCriteria(columnName = "staff_type", value = StaffType.STORY_AUTHOR, enumType = EnumType.STRING)) private Set<Staff> storyAuthors = Sets.newHashSet(); } |
W ten sposób nasza implementacja JPA, np. Hibernate, wiedziałaby, że aby utworzyć relację między filmem i scenarzystą, musi dodać wpis do tabelki movies_staff
, dodatkowo ustawiając wartość kolumny staff_type
na "STORY_AUTHOR"
. Abstrahuję tutaj od potencjalnych problemów wydajnościowych. Star Trek API, z maksymalnie kilkoma tysiącami encji w każdej z tabelek, z pewnością nie cierpiałoby z powodu problemów z wydajnością. Od strony SQL-a takie rozwiązanie również nie wydaje mi się niemożliwe.
Any help?
Byłoby wdzięczny za sugestie, czy da się opisywany problem jakoś rozwiązać. Może istnieją jakieś przeciwskazania do implentacji tego rodzaju funkcjonalności? A może istnieje ona w Hibernate, ale nie w JPA?
Wczoraj zadałem podobne pytanie na StackOverflow. Okazało się, że to, o czym piszę, rzeczywiście nie jst możliwe w JPA, ale jest możliwe do osiągnięcia z użyciem DataNucleus, frameworka, który również implementuje JPA.
Czy zmienię implementację JPA dla jednej funkcjonalności? Pewnie nie, ale dobrze mieć opcje.