System encji i tabel w serwisie takim jak np. IMDB czy Filmweb

0

Piszę serwis podobny do IMDB lub Filmweb i obecnie próbuję zaprojektować bazę do filmów i kontrybucji. Może przedstawię sposób dodawania takiego filmu.

Najpierw dodany film musi trafić do "poczekalni" i czekać na weryfikację przez administratora

@Entity
@Table(name = "movies_lounge")
@Data
public class MovieLoungeEntity {}

jeśli film przejdzie pomyślnie weryfikację, jest dodawany do głównej tabeli

@Entity
@Table(name = "movies")
@Data
public class MovieEntity {}

Tu jest jeszcze w miarę OK.

Ale w takim serwisie musi być również możliwość edycji danych filmów.
Na przykład chcemy edytować opis, więc tworzę encję tylko z polami id_movie, id_author, description i edycja trafia do poczekalni

@Entity
@Table(name = "movies_descriptions_lounge")
@Data
public class MovieLoungeEntity {}

po weryfikacji również trafia do swojej głównej tabeli edycji opisów

@Entity
@Table(name = "movies_descriptions")
@Data
public class MovieDescriptionLoungeEntity {}

I taki sposób musi być stosowany dla wszystkich edytowanych pól, co daje kilkadziesiąt tabeli i encji. Po prostu masakra. Taki sposób jest stosowany, aby potem można było pobrać np. listę edytowanych opisów itd. W obecnej fazie pomijam punktacje za edycje konkretnych elementów, bo to wgl. system miałby z 200 tabel.

Ma ktoś jakiś pomysł, jak można to zaprojektować optymalniej?

EDIT: Rozkminiłem jak nie tworzyć dwóch tabel dla edycji czyli poczekalni i właściwej, a tylko jedną. Stworzę wartość enum STATUS i będą dostępne trzy statusy 'Czeka, Zaakceptowane, Odrzucone'.

1

Możesz mieć kilka encji wskazujących na tę samą tabelę w bazie danych. Ogólnie ORM-y nie służą do tego, żeby zrobić kopię 1:1 struktury bazy danych w Javie. To jest gwarantowany przepis na problemy.

Ponadto z takim podejściem jest taki problem, że np. "film" może być czymś innym w jednej części aplikacji, a czymś innym w drugiej (tzn. dwie części aplikacji mogą potrzebować rozłącznych zbiorów informacji o filmie). W naiwnym podejściu robisz 1:1 encję z tabelą, która próbuje pogodzić te dwa światy, przez co zmiana w jednej części aplikacji może się negatywnie odbić na drugiej. Zapoznaj się z pojęciem bounded context: https://martinfowler.com/bliki/BoundedContext.html , spróbuj poznajdować sobie takie konteksty w swojej aplikacji i organizować encje wokół nich.

6

Offtop:
nie do końca odpowiedź, ale tez baza filmów.... za to super prezentacja:

może Cię natchnie

1
Klawiatur napisał(a):

I taki sposób musi być stosowany dla wszystkich edytowanych pól, co daje kilkadziesiąt tabeli i encji.

Nie dla wszystkich edytowanych pól, tylko wszędzie tam, gdzie jeden film może mieć kilka elementów podrzędnych, np. kilka recenzji. Film ma jedną datę produkcji i na pewno nie twórz do tego osobnej tabeli.

W którymś momencie można zdecydować się na grupowanie elementów różnego rodzaju w jednej tabeli. Właśnie jakimś enumem albo przez dyskryminator. Zobacz Hibernate User Guide, rozdział 12.1.2 Single Table, podrozdział Discriminator.

Postanowiłem zrobić sobie testowo taki model, ale wywaliłem się na tym. Okazuje się, że Hibernate ma problemy z pewnym typem odwzorowania przy używaniu dziedziczenia: one to many mapping to a property of superclass. Poniżej próbka, która działa na EclipseLinku.

//Not using setters for brevity.
@Entity
public class Movie {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  public int id;

  @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL)
  public Set<MovieReview> reviews;
  @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL)
  public Set<MovieDescription> descriptions;

  public Movie()
  {
    this.reviews = new HashSet<MovieReview>();
    this.descriptions = new HashSet<MovieDescription>();
  }
}
@Entity
@Table(name = "MOVIE_INFO")
@DiscriminatorColumn(name = "typ")
public class MovieInfo
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  public int id;

  @ManyToOne
  public Movie movie;
}
@Entity
@DiscriminatorValue(value = "102")
public class MovieDescription extends MovieInfo
{
  public MovieDescription() {}

  public MovieDescription(Movie movie) {
    this.movie = movie;
  }
}
0

Zanim wszedłem tu na forum, obaczyć odpowiedzi to obecnie to wygląda tak
Movie entity https://pastebin.com/PbBnN47d właściwe filmy
Edition entity https://pastebin.com/46K2BE0R poczekalnia ( na razie okrojone bez listy osób, gatunków itd.)
Celebrity entity https://pastebin.com/r4q1MACf osoby

i okrojony serwis https://pastebin.com/KZfMwsbQ

Teraz po przemyśleniach wychodzi, że z opisów, z tytułów w kilku narodowościach, opisach itd. trzeba zrobić listy. Roboty masa.

@jarekczek: a jakby w klasie 'MovieInfo' zamienić adnotację 'Entity' na 'MappedSuperclass' to chyba działałoby. Edit: Czegoś nie rozumiem. W Twojej sytuacji jest tworzona tablica 'MovieDescription', czy to tak jakby należy do tablicy 'MOVIE_INFO' i nie tworzy tablicy 'MovieDescription'?

0

Znalazłem w necie film dotyczący stosowanie tego Inheritance . Obecnie kod wygląda w taki sposób

@Entity
@Table(name = "movies")
@Data
public class MovieEntity {

    @Id
    @Column(unique = true, updatable = false)
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL)
    private Set<MovieDescription> description;
}

@Entity
@Table(name = "movies_info")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class MovieInfo {

    @Id
    @Column(unique = true, updatable = false)
    @GeneratedValue
    private Long id;

    @ManyToOne
    public MovieEntity movie;
}

@Entity
@DiscriminatorValue(value = EditType.Values.DESCRIPTION)
public class MovieDescription extends MovieInfo {

    private String description;
    private String language;
}

ale wywala błąd

Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.core.jpa.entity.MovieDescription.movie in com.core.jpa.entity.MovieEntity.description

tak jakby MovieDescription nie dziedziczyło obiektu 'movie'?

EDIT: Zamotałem się trochę. To jest ten błąd o którym pisałeś. Czyli wychodzi, że jest niemożliwe do wykonania w ten sposób?

EDIT: Problem chyba rozwiązany, bo program się skompilował. Dodałem 'targetEntity = MovieInfo.class'.

@jarekczek: A jak zaprojektowałbyś taką jakby poczekalnię, gdzie zmiany czekałyby na akceptację przez administratora?

0

A dlaczego by nie zrobic "poczekalni" w tej samej enci dodajac enum "public", "version"
Dodatkowo z polami createdAt, editedAt czy cos w tym stylu.
I teraz wersje wyswietlasz ze statusem odpowiednim i ostateczny jako "public"

Nawet spokojnie pokusilbym sie jeszcze o pole z hierarchia aby ladnie adminowi wyswietlic drzewko wersji

0

@john_doe: Chyba będę musiał zrobić coś w ten deseń, bo nie mam wyjścia. Nie będę przecież tworzył dwa razy więcej encji.

@jarekczek: Na razie muszę w zrobić to w ten sposób. Nie mam siły bawić się z podmianą Hibernate na EclipseLink, to dużo kodu musiałbym pozmieniać. Co o tym sądzisz?

@Entity
@Table(name = "movies")
@Data
public class MovieEntity {

    @Id
    @Column(unique = true, updatable = false)
    @GeneratedValue
    private Long id;


    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "movie_id")
    private Set<MovieBoxOffice> boxOffices;
}

@Entity
@Table(name = "movies_info")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class MovieInfo {

    @Id
    @Column(unique = true, updatable = false)
    @GeneratedValue
    private Long id;
}

@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@DiscriminatorValue(value = EditType.Values.DESCRIPTION)
public class MovieDescription extends MovieInfo {

    private String description;

    @ManyToOne
    private UserEntity user;
}

W ten sposób mam również kolumnę w tablicy z ID filmu, ale nie ma tego w encji MovieInfo. Na lepsze rozwiązanie nie wpadłem. W tym rozwiązaniu problem największy jest taki, że gdybym chciał przeszukać bazę pod względem gatunku filmu, to muszę pobierać każdy film i sprawdzać czy ma na liście gatunków konkretny gatunek, dlatego że np. encja MovieGenre nie posiada kolumny 'movie_id', bo ta kolumna jest tylko dla encji MovieEntity. Mam jeszcze obawy, że skoro wszystko ładowanej jest do jednej tablicy, to taka tablica chwila moment będzie zawalona. Dajmy przykład taki serwis Filmweb. Ten serwis posiada około 6000000 filmów. Każdy film posiada przykładowo po kilka tytułów, daty premier, gatunki itd. Na jeden film może nazbierać się około 100 kontrybucji. Mnożąc to razy 6000000 filmów, to mamy 60 000 000 wierszy w jednej tabeli MovieInfo. Czyli jest to mało wydajne.

1

Zmiana z Hibernate na EclipseLink to bardzo mała ingerencja. Zobacz ten commit. Są zmiany w plikach build.gradle (u ciebie może to być maven a nie gradle - niewielka różnica), EmfProvider.java (wprowadzenie nazwy persistence-unit, bo tak chciał EclipseLink) i persistence.xml. Reszta nieistotna.

Ale jak chcesz mieć wszystko w osobnych tabelach, to dasz radę na Hibernate, przez mappedSuperclass.

1
Klawiatur napisał(a):

W ten sposób mam również kolumnę w tablicy z ID filmu, ale nie ma tego w encji MovieInfo. Na lepsze rozwiązanie nie wpadłem. W tym rozwiązaniu problem największy jest taki, że gdybym chciał przeszukać bazę pod względem gatunku filmu, to muszę pobierać każdy film i sprawdzać czy ma na liście gatunków konkretny gatunek, dlatego że np. encja MovieGenre nie posiada kolumny 'movie_id', bo ta kolumna jest tylko dla encji MovieEntity. Mam jeszcze obawy, że skoro wszystko ładowanej jest do jednej tablicy, to taka tablica chwila moment będzie zawalona. Dajmy przykład taki serwis Filmweb. Ten serwis posiada około 6000000 filmów. Każdy film posiada przykładowo po kilka tytułów, daty premier, gatunki itd. Na jeden film może nazbierać się około 100 kontrybucji. Mnożąc to razy 6000000 filmów, to mamy 60 000 000 wierszy w jednej tabeli MovieInfo. Czyli jest to mało wydajne.

Podoba mi się twój tok myslenia. Też sądze, że bazy danych SQL nadają się tylko do małych tabelek, do zabawy :-)
Prawdziwy hardkor trzyma to w RAM:

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