Hibernate, Spring-Data i brak operatora union all

0

OPIS PROBLEMU

Chciałbym zrobić menadżera plików. Mam dwie encje:

  1. DirectoryEntity, która reprezentuje folder - encja jest powiązana sama ze soba na zasadzie dziecko-rodzic po to aby uzyskać strukture drzewiastą, podkatalogi itd. Ponadto każdy folder może zawierać wiele plików (relacja 1:n z encja FileEntity)

  2. FileEntity - reprezentuje plik

Załóżmy teraz, że chce wyświetlić listę plików z obsługą stronnicowania. W tym celu mogę sobie napisać metodę w repozytorium, której kod jest następujący:

@Query("select f.id, f.name from com.entity.media.FileEntity f")
Page<Object[]> findFiles(Pageable pageable);

Wszystko działa ok, ale normalnie powinienem wyświetlić zarówno foldery jak i pliki. Najprościej byłoby użyć unii i zmodyfikować metodę do postaci:

@Query("select d.id, d.name from com.entity.media.DirectoryEntity d " +
"union all select f.id, f.name from com.entity.media.FileEntity f")
Page<Object[]> findFilesAndDirectories(Pageable pageable);

Problem tylko w tym, że Hibernate, którego używam najwyrażniej nie obsługuje unii.

PRÓBY ROZWIĄZANIA PROBLEMU

Pomyślałem sobie, że problem obejdę tworząc widok w swojej bazie danych (PostgreSQL) i... nie myliłem się. Stworzyłem sobie widok:

CREATE VIEW media_view AS
select id, name from media_directories
union all
select id, name from media_files

oraz encję

@Entity(name = "media_view")
public class MediaView {
    @Id
    private Long id;

    @Column(name = "name")
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

I w swoim repozytorium mam teraz metodę

@Query("select m.id, m.name from media_view m")
Page<Object[]> findFilesAndDirectories(Pageable pageable);

Działa jak trzeba... ale problem w tym, że w wersji rozwojowej tabele są usuwane i tworzone na nowo podczas startu aplikacji (opcja "hbm2ddl.auto" ustawiona na "create") a co za tym idzie zostanie utworzona tabela media_view przez co trudno utworzyć widok o tej samej nazwie.

PYTANIA

  1. Czy można pominąć tworzenie tabeli dla encji MediaView?
  2. Czy można podmienić zapytanie SQL, które tworzy tabelę dla tej akurat encji (wtedy umieściłbym tam zapytanie, które tworzy widok).?
  3. Czy można problem rozwiązać inaczej? Jak?
2
  1. Pierwsza myśl to zmiana modelu danych. W linuxie mówią wszystko jest klikiem (katalog także) może pójście w tym kierunku pozwoli łatwiej modelować system plików.
0

Pomysl jak najbardziej warty rozwarzenia. Jednak dobrze by było gdyby dało się problem rozwiązać inaczej (jakoś zasymulować działanie unii), ponieważ takie rzeczy jak operator UNION ALL mogą się jednak przydać w innych przypadach i wolałbym mieć na to sposób. Mimo wszysto dzięki za odpowiedz.

1

Hibernate pozwala na tworzenie czegoś takiego jak database objects w ramach plików z mapowaniami...

<database-object>
   <create>create or replace view yourView</create>
   <drop>drop view yourView</drop>
   <dialect-scope name='org.hibernate.dialect.Oracle9Dialect' />
</database-object>

Powinno pomóc http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch05.html#mapping-database-object

0

Chyba nie do konca o to chodzi (albo ja nie do konca zrozumialem Twoja koncepcje rozwiazania problemu). Jezeli dobrze rozumiem to database objects, o ktorych wspomniales umozliwia mi np. stworzenie widoku w bazie danych. Mnie chodzilo raczej o to by hibernate nie tworzyl mi tabeli dla pojedynczej encji.

Moge sobie stworzyc widok w sposob, ktory zaproponowales natomiast jak sie pozniej do takiego widoku odwolac za pomoca HQL / JQL? Nie widze innej mozliwosci jak stworzyc encje, ktorej "wydaje sie" ze jest mapowana na tabele podczas gdy mapowana jest na widok. Tylko, ze jak te encje stworze to w wersji rozwojowej podczas startu aplikacji Hibernate utworzy mi tabele dla encji symulujacej widok. Jak juz sobie te tabelke stworzy to bedzie chcial stworzyc widok zdefiniowany w database-object - oczywiscie o tej samej nazwie co wczesniej utworzona tabela.

Gdybym nie musial tworzyc tej encji aby odwolac sie do widoku to byloby fajnie, ale chyba jednak nie mam innego wyjscia poniewaz jezeli tego nie zrobie to Hibernate wywala blad ze cos tam nie jest zmapowane,

0

Ale nie powinno to stanowić problemu. Wystarczy utworzyć encję read-only i mapować ja jak każdą inną. Hibernate sprawdzi czy istnieje już taki obiekt w bazie i będzie to umiał sobie odpowiednio połączyć.

0

Mozna wiedziec jak mam utworzyc encje read-only? Probowalem ustawic atrybut mutable na false. Moj plik xml wyglada tak:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
    <class name="com.db.MediaView" mutable="false">
        <id column="id" name="id" type="java.lang.Long"/>
    </class>

    <database-object>
        <create>
            CREATE VIEW mediaview AS
            SELECT id, name FROM media_directories
            UNION ALL
            SELECT id, name FROM media_files
        </create>
        <drop></drop>
        <dialect-scope name="org.hibernate.dialect.PostgreSQL9Dialect"></dialect-scope>
    </database-object>
</hibernate-mapping>

Niestety za kazdym razem tworzy mi sie tabela "mediaview" a nie widok. Widok jest tworzony dopiero wtedy kiedy zakomentuje kod odpowiedzialny za mapowanie encji. Dodam, ze uzywam Hibernate w wersji 4.3.6.Final.

1

Hakierstwo:

    <database-object>
        <create>
            DROP TABLE mediaview;
            CREATE VIEW mediaview AS
            SELECT id, name FROM media_directories
            UNION ALL
            SELECT id, name FROM media_files
        </create>
        <drop></drop>
        <dialect-scope name="org.hibernate.dialect.PostgreSQL9Dialect"></dialect-scope>
    </database-object>

Najpierw usuwasz tabelę, a potem tworzysz widok. Hibernate jest na tyle tempy, że nie ogarnie tego....

//edit: jak tak nie ruszy to zawsze można podpiąć plik z sqlkami zamiast grzebania w xmlu.

0
Koziołek napisał(a):

Najpierw usuwasz tabelę, a potem tworzysz widok. Hibernate jest na tyle tempy, że nie ogarnie tego....

Teraz dziala :) Co ciekawe probowalem tego sposobu wczesniej i nie zadzialalo. Nie wiem tylko czy to sprawa tego, ze wczesniej mapowalem encje adnotacjami a nie w pliku XML czy po prostu popelnilem jakis blad typu brak srednika miedzy zapytaniem DROP a zapytaniem CREATE (nie chce mi sie juz tego sprawdzac).

Rozwiazanie tak jak napisalem wyzej, dziala ok. Nie jest to do konca fajna opcja poniewaz nie jestem zwolennikiem tworzenia rozwiazan w oparciu o takie sztuczki, ale mimo wszystko zostawie to w takiej formie jak jest, przynajmniej na jakis czas. Mam nadzieje, ze w kolejnych wersjiach Hibernate cos z tym zrobia, poniewaz troche dziwi mnie, ze sa problemy z operatorem UNION ALL czy odwolaniami do widokow w sposob bezposredni. Nie orientujesz sie czy tworcy Hibernate maja jakies plany z tym zwiazane? Podobno operator UNION ALL dziala w Eclipse Link wiec w Hibernate tez moglby dzialac :)

Koziołek napisał(a):

//edit: jak tak nie ruszy to zawsze można podpiąć plik z sqlkami zamiast grzebania w xmlu.

Mozesz rozwinac?

1

Zamiast tworzyć wpisy w XMLu wystarczy utworzyć plik import.sql tak by był dostępny w classpath Hibernate Tools. Zostanie on uruchomiony na samym końcu.
Inna opcja to implementacja AuxiliaryDatabaseObject, ale to już w jakiś dziwnych przypadkach.

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