poniedziałek, 18 maja 2009

Hibernate, dziwne zachowanie InheritanceType.SINGLE_TABLE

Dziś trafiłem na bardzo ciekawy problem. Korzystałem z obiektów, z dziedziczeniem oraz mapowaniem z wykorzystaniem pojedyńczej tabeli. Tego typu rozwiązanie ma tą zaletę, że jest szybkie, oraz tą wadę, że potrzeba trochę więcej miejsca w bazie. Dla mnie to drugie nie było problemem w tej konkretnej sytuacji, a ponieważ zależało mi na szybkości, to ta metoda na pozór wyglądała bardzo ciekawie.

W skrócie moje klasy wyglądały tak:


@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("PLAYER_SHORT_INFO")
public class PlayerShortInfo implements IsSerializable {

@Id @GeneratedValue
@Column(name = "ID", unique = true, nullable = false)
private long id;
[...]
}

@Entity
@DiscriminatorValue("PLAYER_INFO")
@PrimaryKeyJoinColumn(name="ID")
public class PlayerInfo extends PlayerShortInfo implements IsSerializable {
@ManyToOne(optional = false, cascade = {CascadeType.REFRESH, CascadeType.PERSIST}, fetch = FetchType.EAGER)
PlayerType type;
[...]
}


Nigdy nie tworzyłem obiektów klasy PlayerShortInfo.Dziedziczneie było zrobione głownie dla ograniczenie ilości danych przesylanych klientowi, gdy potrzebuje tylko danych skróconych (ale na przykład nie dla jednego obiektu, a dla 100). Takie rozwiązanie wydało mi sie proste i wydajne. Niestety, dziś okazało sie inaczej.

Klasa PlayerInfo, jest klasa która nigdy nie jest przekazywana do warstwy WEB. Jest wykorzystywana tylko w logice biznesowej, a wiec nawet nie było potrzeby, aby implementowała IsSerializable (wymagane przez GWT). W czasie testów, wyszło jednak, ze GWT nie może przetransportować listy zawierającej PlayerShortInfo ponieważ PlayerType (!!!) nie implementuje IsSerializable. Po sprawdzeniu, okazało sie, ze zeczywiscie nie implementuje, tylko ze PlayerType nigdy nie miał być przesyłany do warstwy WEB.

Po dłuższych poszukiwaniach, okazało sie ze SELECT: (List)em.createQuery("select d from PlayerShortInfo d order by id").getResultList(); zwraca listę nie PlayerShortInfo ale PlayerInfo

Wnioski:
InheritanceType.SINGLE_TABLE jest nieco mylące. Obiektem ładowanym z bazy jest zawsze obiekt który został zapisany, nawet jeżeli w Select jest jasno podane ze chcemy załadować obiekt wyższego rzędu. Co sie dzieje, jest wykonywane załadowanie obiektu który został zapisany a następnie rzutowanie go na element docelowy. Sprawa jest bardziej skomplikowana, bo jeżeli dziecko jest obiektem ciężkim, to pomimo ze chcemy załadować lekkiego rodzica, załadowany zostanie ciężki obiekt dziecka, razem ze wszystkimi atrybutami które ono posiada (na przykład listami innych obiektów).

Rozwiązanie:
Moim zdaniem nie ma dobrego rozwiązania. Trzeba być świadomym problemu i stosować takie rozwiązania w swojej aplikacji, aby nie było potrzeby stosowania tego rodzaju dziedziczenia.

poniedziałek, 11 maja 2009

Duża ilość elementów w GWT

GWT generalnie jest fajną platformą, ale niestety sam javascript nie jest najszybszy. Okazuje się, że dodanie dużej ilości elementów załadowanych z bazy i wstawienie ich do tabeli stanowi spory problem. Na przykład, ładując 500 elementów i wstawiając je do FlexTable albo do Grid, strona ładuje się dość długo, szczególnie na słabszych komputerach. Ponieważ zależało mi na szybkości ładowania elementów, postanowiłem temat zbadać i spróbować coś z nim zrobić.

Zarówno Grid jak i FlexTable, a w zasadzie również VerticalPanel oraz HorizontalPanel są oparte o tablice. Tablica jako taka jest elementem dość skomplikowanym w budowie, a dodatkowo GWT dodaje dość dużo kodu sprawdzającego, warunkowego oraz debugu. Cały ten kod powoduje, że Całość działa o wiele wolniej.

Ponieważ efekt zbliżony do tabeli można osiągnąć również przy pomocy arkusza styli CSS oraz zwykłych div, postanwoiłem spróbować przerobić standardową tabelę na pseudotabelę zrobioną przy pomocy DIV z flow:left; GWT posiada kilka elementów które mi na to pozwalają. Pierwszy z nich to FlowPanel który jest tłumaczony na DIV i pozwala na dodanie innych komponentów do środka. Drugi to Label, SimplePanel oraz Hyperlink pozwalające na dodanie innych elementów.

Są dwie zasady jeżeli chcemy osiągnąć efekt właściwy.
  • Wszystkie elementy muszą mieć na stałe ustawioną wysokośc oraz szerokość
  • Sumaryczna szerokość elemtnów wewnątrz "tabeli" musi być równa szerokości "tabeli"
Wygenerowany kod będzie wyglądał tak:


<div style="width:200px;display:block;">
<div style="float:left;width:100px;height:20px;">Nazwa 1</div>
<div style="float:left;width:100px;height:20px;">Wartosc 1</div>
<div style="float:left;width:100px;height:20px;">Nazwa 2</div>
<div style="float:left;width:100px;height:20px;">Wartosc 2</div>
</div>


Efekt wyglada tak:
Nazwa 1
Wartosc 1
Nazwa 2
Wartosc 2


Z wygladu mamy tabele, a wiec to o co chodzi.

To samo mozna osiagnac w GWT, tylko style nalezy przeniesc do arkusza styli.

FlowPanel content = new FlowPanel();
Label l1 = new Label("Nazwa 1");
Label w1 = new Label("Wartosc 1");
Label l2 = new Label("Nazwa 2");
Label w2 = new Label("Wartosc 2");

content.setStyleName("table_main");
l1.setStyleName("table_field");
l2.setStyleName("table_field");
w1.setStyleName("table_field");
w2.setStyleName("table_field");

content.add(l1);
content.add(l2);
content.add(w1);
content.add(w2);

initWidget(content);

STYLE:

.table_main{
width:200px;
display:block;
}

.table_field{
float:left;
width:100px;
height:20px;
}



Ponieważ FlowPanel nie wymaga dużo sprawdzeń i inicjalizacji to jego utworzenie jest bardzo proste. Dodanie kolejnych elementów nie wymaga tworzenia struktury drzewa, tak jak w przypadku kontenerów opartych o tablice. Dzięki temu, ilość kodu javascript który musi zostać wykonany jest dużo mniejsza, a co za tym idzie jest mocno zauważalny wzrost wydajności tego rozwiązania w stosunku do standardowej tabeli.
Wada tego rozwiązania jest taka, że jednak nie jest to standardowa tabela, a co za tym idzie, nie ma możliwości skalowania tabeli. Na szczęście w większości dzisiejszych layoutów wielkości są zablokowane na stałe, a więc ten problem dotyczy niewielkiej ilości przypadków.

PS. Nie mieżyłem wzrostu wydajności, ale wzrost wydajności był zauważalny bez mieżenia. Strona oparta o tabele ładowała się wolno. Strona oparta o to rozwiązanie ładowała się w zasadzie odrazu.