Przewodnik – pierwsze kroki z Hibernate
Transkrypt
Przewodnik – pierwsze kroki z Hibernate
Jarosław Henke, 6 kwietnia 2016r Przewodnik – pierwsze kroki z Hibernate Na podstawie docs.jboss.org dla Hibernate wersja 5.1 - tłumaczenie i skrót Stan wiedzy z dnia: 2016-02-11 14:23:58 CST Spis Treści Spis Treści.............................................................................................................................2 Wstęp.....................................................................................................................................3 1. Pozyskiwanie Hibernate................................................................................................4 1.1. Moduły Hibernate...................................................................................................4 1.3. Zawartość repozytorium Maven..........................................................................5 2. Używanie rodzimych interfejsów programistycznych (API) Hibernate z mapowaniem hbm.xml......................................................................................................6 2.1. Plik konfiguracyjny Hibernate..............................................................................6 2.2. Klasa encji Java.........................................................................................................7 2.3. Plik mapujący...........................................................................................................7 2.4. Przykładowy kod.....................................................................................................9 3. Używanie rodzimego Hibernate API z Annotation Mappings............................10 3.1. Plik konfiguracyjny Hibernate............................................................................10 3.2. Klasa encji Javy opatrzona przypisami (annotated entity Java class).........................10 3.3. Przykładowy kod...................................................................................................11 4. Używanie Java Persistence API (JPA).........................................................................12 4.1. persistence.xml.....................................................................................................12 4.2. Klasa encji z przypisami.......................................................................................12 4.3. Przykładowy kod...................................................................................................12 5. Użycie Envers.................................................................................................................13 5.2. 4.2. Klasa encji Java z przypisami........................................................................13 5.3. Przykładowy kod...................................................................................................14 22 Wstęp Praca jednocześnie z oprogramowaniem zorientowanym obiektowo w połączeniu z relacyjnymi bazami danych może nie być zbyt wygodna i pochłaniać zbyt wiele czasu. Wpływa na to niedopasowanie paradygmatów miedzy obiektową reprezentacją danych w stosunku do relacyjnych baz danych. Przekłada się to bezpośrednio na koszty produkcji, bo przecież czas to pieniądz. Hibernate – framework typu ORM (Object/Relational Mapping) jest jednym z najpopularniejszych rozwiązań tego problemu w środowisku Java. Pojęcie Obiect/Relational Mapping nawiązuje do metody sporządzenia powiązań (mapowania) pomiędzy modelem reprezentacji obiektowej w obiektach Javy, a modelem relacyjnym w bazach danych. Pomimo, że posiadanie gruntownego przygotowania w zakresie SQL nie jest wymagane, by używać Hibernate, to jednak podstawowa wiedza w tym zakresie pomaga zrozumieć Hibernate szybciej i bardziej w pełni. Znajomość zasad modelowania danych jest tu szczególnie ważna. Hibernate troszczy się o mapowanie obiektów z klas Javy do tabel bazodanowych dbając też o mapowanie typów danych z Javy do SQL. W dodatku dostarcza zapytań bazodanowych i funkcji wyszukiwania danych. To znacznie redukuje czas pracy programisty, który w przeciwnym wypadku musiałby ręcznie tworzyć zapytania za pomocą SQL-a i JDBC. Celem projektowym Hibernate jest zwolnienie developera z 95% koniecznych zadań dotyczących programowania trwałości obiektów, poprzez wyeliminowanie potrzeby fizycznego sklejania zapytań i używania SQL w połączeniu z JDBC. Jednakże w odróżnieniu od wielu innych rozwiązań tego typu Hibernate nie rezygnuje z mocy SQL-a. Nie ukrywa możliwości skorzystania z tej technologii przez programistę i czyni ją dostępną i ważną jak zawsze. Hibernate może nie jest najlepszym rozwiązaniem dla data-centrycznych aplikacji, w których cała logika biznesowa sprowadza się do używania jedynie procedur składujących. Jest bardziej użyteczny w zastosowaniach z modelami domen zorientowanymi obiektowo w logice biznesowej Javy warstwy środkowej. Z pewnością jednak Hibernate w sposób oczywisty pozwala na usunięcie lub skrócenie kodu specyficznego dla klienta SQL, usprawniając bieżące zadanie translacji wyniku zapytania z reprezentacji tabelarycznej do grafu obiektów. 33 1. Pozyskiwanie Hibernate 1.1. Moduły Hibernate Funkcjonalność Hibernate jest podzielona na pewną ilość niezależnych, wyizolowanych modułów. hibernate-core Główny moduł (jądro) Hibernate. Definiuje jego funkcje ORM oraz interfejsy programowe aplikacji (Apis) jak również różne interfejsy SPI (Sernice Provider Interface) zintegrowane z jądrem. hibernate-entitymanager Definiuje wsparcie Hibernate dla JPA hibernate-java8 Wsparcie dla platformy Java8 stosującej specyficzne typy danych takich jak JSR 310 Date/Time w modelu dziedziny. hibernate-envers Historyczne wersjonowanie encji Hibernate. hibernate-spatial Wsparcie Hibernate dla danych typu Spatial/GIS. hibernate-osgi Wsparcie Hibernate do uruchamiania w kontenerach OSGi. hibernate-c3p0 Integruje bibliotekę połączeń współdzielonych C3P0 z Hibernate. hibernate-hikaricp Integruje bibliotekę połączeń współdzielonych HikariCP z Hibernate. hibernate-proxool Integruje bibliotekę połączeń współdzielonych Proxool z Hibernate. 44 hibernate-ehcache Integruje bibliotekę keszującą Ehcache z Hibernate jako dostawcę kesza drugiego poziomu. hibernate-infinispan Integruje bibliotekę keszującą Infinispan z Hibernate jako dostawcę kesza drugiego poziomu. 1.2. Pobieranie paczek instalacyjnych Zespół Hibernate umieścił pakiety instalacyjne w systemie SourceForge w formatach TGZ i ZIP. Każda z paczek zawiera pliki JAR, dokumentację, kody źródłowe i inne dobrodziejstwa. Pakiety znajdują się pod adresem: https://sourceforge.net/projects/hibernate/files/hibernateorm/. Ich struktura przedstawia się w sposób następujący: lib/required/ katalog zawierający hibernate-core jar i wszystkie potrzebne pliki zależne. Wszystkie te jar-y są niezbędne niezależnie od tego, która z funkcjonalności Hibernate będzie używana. lib/jpa/ katalog zawierający hibernate-entitymanager jar jak również wszystkie zależności (uzupełniające te w lib/required/) lib/java8/ katalog zawierający hibernate-java8 jar jak również wszystkie zależności (uzupełniające te w lib/required/) lib/envers katalog zawierający hibernate-envers jak również wszystkie zależności (uzupełniające te w lib/required/ i lib/jpa/). lib/spatial/ katalog zawierający hibernate-spatial jar jak również wszystkie zależności (uzupełniające te w lib/required/) lib/osgi/ katalog zawierający hibernate-osgi jar jak również wszystkie zależności (uzupełniające te w lib/required/ i lib/jpa/) lib/optional/ katalog zawierający pliki typu jar potrzebne do różnych połączeń wspólnych oraz integracji keszu drugiego poziomu udostępnianego przez Hibernate, wraz z ich zależnoścami. 1.3. Zawartość repozytorium Maven Autoryzowanym repozytorium produktów Hibernate jest repozytorium JBoss Maven. Produkty Hibernate są zsynchronizowane z centralą Maven niejako automatycznie (małe przerwy mogą się zdarzyć). Zespół odpowiedzialny za repozytorium JBoss Maven utrzymuje szereg stron typu Wiki, które zawierają ważne i przydatne informacje. http://community.jboss.org/docs/DOC-14900 - ogólne informacje o repozytorium. 55 http://community.jboss.org/docs/DOC-15170 - Informacje o instalowaniu repozytoriów JBoss w celu udziału w rozwoju projektu. http://community.jboss.org/docs/DOC-15169 - Informacje o zainstalowaniu dostępu do repozytorium w celu uzyia projektów JBoss jako części swojego wlasnego oprogramowania. Produkty Hibernate ORM są publikowane w domenie grupy org.hibernate . 2. Używanie rodzimych interfejsów programistycznych (API) Hibernate z mapowaniem hbm.xml 2.1. Plik konfiguracyjny Hibernate W tym tutorialu, plik hibernate.cfg.xml definiuje informacje konfiguracyjne Hibernate. Elementy connection.driver_class, connection.url, connection.username oraz connection.password <property/> zawierają informacje niezbędne do ustanowienia połączenia JDBC. Ten samouczek wykorzystuje bazę danych H2 in-memory, zatem wartości tych własności są właściwe dla prawidłowego działania H2 w trybie in-memory. connection.pool_size jest używane do konfiguracji liczby połączeń wbudowanych w pulę połączeń Hibernate. Wbudowana pula połączeń Hibernate nie służy w żadnym wypadku do użytku produkcyjnego. Brakuje w niej kilku cech znajdujących się w production-ready connection pools. Właściwość dialect specyfikuje odpowiedni wariant SQL współpracujący z Hibernate. W większości przypadków, Hibernate jest zdolne do wykrycia który rodzaj dialektu jest używany. Jest to szczególnie przydatne jeśli celem aplikacji jest jednocześnie wiele baz danych. Własność hbm2ddl.auto umożliwia automatyczną generację schematu bazy bezpośrednio do bazy danych. Na końcu do konfiguracji dodawane są pliki mapujące klasy trwałości. Atrybut resource elementu <mapping/> powoduje, iż Hibernate podejmuje próbę zlokalizowania tego mappingu używając zasobu tej ścieżki w wyszukiwaniu java.lang.ClassLoader. 66 Jest wiele dróg i opcji do załadowania SessionFactory w Hibernate. Aby poznać więcej szczegółów, patrz wskazówka Natywny Bootstrapping. 2.2. Klasa encji Java Przykładowa klasa encji w tym tutorialu znajduje się na stronie org.hibernate.tutorial.hbm.Event Notatki dotyczące klasy encji W tej klasie wykorzystywany jest standard JavaBean w zapisie getterów i setterów, jak również dostępność pól ustawiona jest na private. Aczkolwiek jest to jedynie zalecane postępowanie, nie jest koniecznie wymagane. Konstruktor bezargumentowy, który jest także w konwencji JavaBean, jest wymogiem dla wszystkich klas trwałości. Hibernate potrzebuje go do stworzenia obiektów za ciebie, używając Java Reflection. Konstruktor powinien być private. Aczkolwiek, pakiet lub widoczność public jest wymagana do generowania runtime proxy i wydajnego wyszukiwania danych bez inkrementacji kodu binarnego. 2.3. Plik mapujący Przykładowy plik mapujacy dla tego tutorialu org/hibernate/tutorial/hbm/Event.hbm.xml. znajduje się w zasobach Hibernate używa metadanych mapujących by określić w jaki sposób ładować i składować obiekty klas trwałości. Plik mapujący Hibernate jest jedynym sposobem dostarczenia środowisku Hibernate tych metadanych. Przykład 1. Mapowanie klasy <class name="Event" table="EVENTS"> ... </class> Funkcje zawierające <varname>class</varname> mapujące element Atrybut name (w kombinacji z atrybutem package z elementu zawierającego <hibernate-mapping/>) jest kwalifikowaną nazwą (FQN) klasy definiującej encję. Atrybut table jest nazwą tabeli bazodanowej, która zawiera dane odpowiadające tej encji. Instancje klasy Event są teraz zamapowane do wierszy tabeli bazodanowej EVENTS. Przykład 2. mapowanie id <id name="id" column="EVENT_ID"> ... </id> Hibernate używa właściwości nazwanej jako <id/> do unikatowej identyfikacji wierszy w tabeli. 77 Element id nie jest potrzebny do mapowania kolumny klucza głównego tabeli ale jest to normalna konwencja. Tabele mapowane w Hibernate nie potrzebuja nawet definiowania kluczy głównych. Jednakże jest to mocno zalecane w celu właściwego zdefiniowania więzów integralności. Dlatego id i klucz główny są używane wymiennie w całej dokumentacji Hibernate Element <id/> tu nazwany jako kolumna EVENT_ID jako klucz główny tabeli EVENTS. To także okresla własność id klasy Event jako własność zawierającą wartość identyfikatora. Element generator informuje Hibernate o tym która strategia jest używana do generowania wartości klucza głównego dla tej encji. Nasz przykład używa prostej inkrementacji. Przykład 3. Mapowanie właściwości <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> Dwa zadeklarowane elementy <property/> są związane z dwoma trwałymi właściwościami klasy Event: date i tittle. Mapowanie właściwości date zwiera atrybut column, ale w przypadku właściwości tittle już tak nie jest. W przypadku nieobecności atrybutu column, Hibernate użyłoby nazwy właściwości jako nazwy kolumny. To byłoby odpowiednie dla właściwości tittle, ale przecież słowo date jest zarezerwowane w większości baz danych i potrzebne jest przypisanie niezarezerwowanego słowa do nazwy kolumny. Mapowaniu tytułu brakuje tez atrybutu typu. Typy deklarowane i używane w plikach mapujących nie są żadnymi typami języka Java ani typami znanymi z SQL-owych baz danych. Zamiast nich mamy specjalne Hibernate mapping types, które są konwerterami tłumaczącymi typy pomiędzy Javą a SQL-em. Hibernate samodzielnie decyduje o właściwej konwersji i mappingu jeśli nie zostały one wyspecyfikowane. Używa tu odwzorowania Javy do wyznaczenia typu Javy dla zadeklarowanej właściwości i następnie standardowego typu mapowania dla tego typu Javy. W niektórych przypadkach automatyczna detekcja może nie przynieść wyboru właściwego lub oczekiwanego, jak to widac w przypadku właściwości date. Hibernate nie jest w stanie rozstrzygnąć czy właściwość typu java.util.Date powinna być zamapowana do SQLowego typu danych DATE, TIME czy TIMESTAMP. Pełna informacja o dacie i czasie jest zabezpieczona przez mapowanie właściwości konwertera timestamp, który identyfikuje klasę org.hibernate.type.TimestampType. Hibernate ustala typy mapujące używając odwzorowania w czasie przetwarzania plików. Ta czynność wydłuża czas mapowania. Jeśli zatem czas wykonania startu jest ważny, należy rozważyć zdefiniowanie używanych typów. 88 2.4. Przykładowy kod Klasa org.hibernate.tutorial.hbm.NativeApiIllustrationTest ilustruje użycie Hibernate native API. Przykłady w tym tutorialu są przedstawione jako testy jednostkowe JUnit, w celu uproszczenia. Korzyścią płynącą z takiego rozwiązania jest pokazanie z grubsza za pomocą setUp i tearDown jak org.hibernate.SessionFactory jest tworzona przy starcie i zamykana na końcu cyklu życia aplikacji. Przyklad 4. Uruchamianie org.hibernate.SessionFactory protected void setUp() throws Exception { // A SessionFactory is set up once for an application! final StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .configure() // configures settings from hibernate.cfg.xml .build(); try { sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory(); } catch (Exception e) { // The registry would be destroyed by the SessionFactory, but we had trouble building the SessionFactory // so destroy it manually. StandardServiceRegistryBuilder.destroy( registry ); } } Na początku metoda setup buduje instancję org.hibernate.boot.registry.StandardServiceRegistry, która włącza informacje konfiguracyjne do roboczego setu usług używanego przez SessionFactory. W tym tutorialu zdefiniowano wszystkie informacje konfiguracyjne w pliku hibernate.cfg.xml zatem nie ma tu nic więcej do dodania. Używając StandardServiceRegistry tworzymy org.hibernate.boot.MetadataSourc es który jest punktem startowym informujacym Hibernate o modelu jego dziedziny. Ponownie, z powodu zdefiniowania tego w pliku hibernate.cfg.xml nie ma tu nic więcej do dodania. org.hibernate.boot.Metadata reprezentuje kompletny, częściowo validowalny widok modelu dziedziny aplikacji na którym będzie bazować SessionFactory. Ostatnim krokiem w procesie bootstrapu jest zbudowanie SessionFactory. SessionFactory jest obiektem bezpiecznego wątku, który raz zapoczątkowany jest w stanie obsłużyć całą aplikację. Akty działania SessionFactory będące instancjami org.hibernate.Session powinny być traktowane jak „jednostki pracy”. 99 Przyklad 5. Zapisywanie encji Session session = sessionFactory.openSession(); session.beginTransaction(); session.save( new Event( "Our very first event!", new Date() ) ); session.save( new Event( "A follow up event", new Date() ) ); session.getTransaction().commit(); session.close(); testBasicUsage() na początku tworzy nowe obiekty Event i zleca im poprzez Hibernate jako zarządcy użycie metody save(). Hibernate w tym momencie przejmuje odpowiedzialność za wykonanie INSERT do bazy danych dla każdej instancji Event. Przyklad 6. Uzyskiwanie listy encji session = sessionFactory.openSession(); session.beginTransaction(); List result = session.createQuery( "from Event" ).list(); for ( Event event : (List<Event>) result ) { System.out.println( "Event (" + event.getDate() + ") : " + event.getTitle() ); } session.getTransaction().commit(); session.close(); Tutaj widzimy przykład użycia języka HQL (Hibernate Query Language) do załadowania z bazy danych wszystkich istniejących obiektów Event poprzez wygenerowanie odpowiedniego zapytania SELECT SQL, wysłania go do bazy danych i umieszczeniu obiektów Event w secie danych wynikowych. 3. Używanie rodzimego Hibernate API z Annotation Mappings 3.1. Plik konfiguracyjny Hibernate Zawartość pliku jest identyczna jak w poprzednim przypadku z jedna ważną różnicą... Element <mapping/> na samym końcu nazywa przypisaną klasę encji opatrzona przypisami (annotated entity class) używając atrybutu class. 3.2. Klasa encji Javy opatrzona przypisami (annotated entity Java class) W tym tutorialu omawiana klasa encji która przestrzega konwencji JavaBean to: org.hibernate.tutorial.annotations.Event . Faktem jest, że ta klasa sama w sobie jest identyczna z klasą encji Javy, tyle tylko że zamiast odrębnego pliku do ustanowienia metadanych są wykorzystywane przypisy. 10 Przykład 7. Identyfikowanie klasy jako encji @Entity @Table( name = "EVENTS" ) public class Event { ... } Przypis @javax.persistence.Entity jest używany do zaznaczenia że dana klasa jest encją. Funkcjonuje to tak samo jak w przypadku tagu mapującego <class/> omawianego przy okazji plików mapujących. Dodatkowo przypis @javax.persistence.Table wyraźnie określa nazwę tabeli. Bez tego, defaultywna nazwa tabeli byłaby EVENT. Przykład 8. Identyfikowanie własności identyfikatora @Id @GeneratedValue(generator="increment") @GenericGenerator(name="increment", strategy = "increment") public Long getId() { return id; } @javax.persistence.Id zaznacza właściwość, która określa identyfikator encji. @javax.persistence.GeneratedValue i @org.hibernate.annotations.GenericGen erator pracują wspólnie, by wskazać, że Hibernate powinno użyć swojej strategii generowania increment dla tej tak oznaczonych wartości identyfikatorów encji. Przykład 9. Identyfikowanie własności podstawowych public String getTitle() { return title; } @Temporal(TemporalType.TIMESTAMP) @Column(name = "EVENT_DATE") public Date getDate() { return date; } Jak w <<hibernate-gsg-tutorial-basic-mapping, własność date wymaga specjalnego traktowania w celu zarejestrowania jej specjalnej nazwy i typu SQL. Atrybuty encji są uważane za domyślnie trwałe, oto dlaczego nie widzimy jakichkolwiek informacji powiązanych z tittle. 3.3. Przykładowy kod org.hibernate.tutorial.annotations.AnnotationsIllustrationTest jest zasadniczo taki sam jak org.hibernate.tutorial.hbm.NativeApiIllustrationTest co jest omówione w http://docs.jboss.org/hibernate/orm/5.1/quickstart/htm l_single/#hibernate-gsg-tutorial-basic-test. 11 4. Używanie Java Persistence API (JPA) 4.1. persistence.xml Poprzednie tutoriale używały specyficznego dla Hibernate pliku konfiguracyjnego hibernate.cfg.xml. JPA jednakże, określa inny proces bootstrapowy, który używa własnego pliku konfiguracyjnego o nazwie persistence.xml. Ten proces bootstrapowy jest określony przez specyfikacje JPA. W środowisku Java™ SE zarządca trwałości (W tym przypadku Hibernate) musi zlokalizować wszystkie pliki konfiguracyjne wyszukując ich ścieżek w zasobie META-INF/persistence.xml. Przykład 10. persistence.xml <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="org.hibernate.tutorial.jpa"> ... </persistence-unit> </persistence> Pliki persistence.xml powinny dostarczać unikalnej nazwy każdej jednostce trwałości. Aplikacje używaja tych nazw do uzyskania referenci do konfiguracji kiedy uzyskują referencję do javax.persistence.EntityManagerFactory . Ustawienia zdefiniowane w elemencie <properties/> są omawiane w Plik konfiguracyjny Hibernate. Tutaj używa sięjavax.persistence -z różnymi odmianami prefiksów w razie potrzeby. Zauważ że pozostawione specyficzne dla ustawień konfiguracyjnych Hibernate nazwy maja teraz prefix hibernate.. Dodatkowo, funkcje elementu <class/> podobnie są zapisane w Plik Konfiguracyjny Hibernate. 4.2. Klasa encji z przypisami Encje są dokładnie takie same jak te opisane w rozdziale 3.2. Klasa encji Javy opatrzona przypisami (annotated entity Java class). 4.3. Przykładowy kod Poprzednie tutoriale używały Hibernate native APIs. Ten tutorial uzywa JPA APIs. 12 Przykład 11. Dostęp do javax.persistence.EntityManagerFactory protected void setUp() throws Exception { entityManagerFactory = Persistence.createEntityManagerFactory( "org.hibernate.tutorial.jpa" ); } Zauważ ponownie, że nazwa jednostki trwałości to org.hibernate.tutorial.jpa, która pasuje do persistence.xml. Przykład 12. Saving (utrwalanie) encji EntityManager entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); entityManager.persist( new Event( "Our very first event!", new Date() ) ); entityManager.persist( new Event( "A follow up event", new Date() ) ); entityManager.getTransaction().commit(); entityManager.close(); Kod podobny do: Przykład 5. Zapisywanie encji. Interfejs javax.persistence.EntityManager jest używany zamiast interfejsu org.hibernate.Session. JPA nazywa tą operację "persist" zamiast "save". Przykład 13. Dostęp do listy encji entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); List<Event> result = entityManager.createQuery( "from Event", Event.class ).getResultList(); for ( Event event : result ) { System.out.println( "Event (" + event.getDate() + ") : " + event.getTitle() ); } entityManager.getTransaction().commit(); entityManager.close(); Znowu, kod całkiem podobny do tego który jest zapisany w Przykład 6. Uzyskiwanie listy encji. 5. Użycie Envers 5.1. persistence.xml Ten plik był omawiany w rozdziale 4. Używanie Java Persistence API (JPA), a dokładnie w podrozdziale dotyczącym persistence.xml i jest dokładnie taki sam. 5.2. 4.2. Klasa encji Java z przypisami Ponownie, the encja jest w większości tym samym co w rozdziale 4.2 Klasa encji Java z przypisami. Główna różnica polega na dodaniu przypisu 13 @org.hibernate.envers.Audited, który informuje Envers by automatycznie śledził zmiany w tej encji. 5.3. Przykładowy kod Kod zapisuje jakieś encje, wprowadza zmianę w jednej z encji a następnie używa Envers API do cofnięcia początkowej korekcie jak również uaktualnionej korekcie. Korekta odsyła do historycznej migawki encji. Example 14. Using the org.hibernate.envers.AuditReader public void testBasicUsage() { ... AuditReader reader = AuditReaderFactory.get( entityManager ); Event firstRevision = reader.find( Event.class, 2L, 1 ); ... Event secondRevision = reader.find( Event.class, 2L, 2 ); ... } Widzimy, że org.hibernate.envers.AuditReader org.hibernate.envers.AuditReaderFactory, javax.persistence.EntityManager. jest wyprodukowany przez który opakowuje Następnie, metoda find odzyskuje dokładne zmiany encji. Pierwsze wywołanie nakazuje znaleźć korektę nr 1 elementu Event, w którym id = 2. Drugie wywołanie nakazuje znaleźć korektę nr 2 elementu Event w którym id = 2. 14