PersistenceContext to zbiór zarządanych encji. Zadządzaniem zajmuje się EntityManager. Śledzi zmiany wykonywane na encjach i utrwala je w bazie (to, kiedy zmiany zostaną zapisane do bazy zależy od flushMode).
PersistenceContext może być transakcyjny lub rozszerzony. Transakcyjny jest zamykany razem z końcem transakcji (a wszyskie encje wchodzące w jego skład zostają odłączone). Aby PC mógł być transakcyjny, musi być zarządzany przez serwer aplikacji (tzn. wstrzyknięty adnotacją lub xml-em). Przykład:
Przy rozszerzonym PC, encje są zarządzane również po zakończeniu transakcji (ale zmiany wprowadzone poza transakcją zostaną utrwalone w bazie dopiero w kolejnej transakcji). Rozszerzony PC może być utworzony i zarządzany ręcznie lub wstrzyknięty do StatefullSessionBean-a. Przykład kodu:
EntityManager mapuje zbiór klas na konkretną bazę danych. Ten zbiór klas to PersistenceUnit. PU można utworzyć zarówno w środowiskach Java EE(serwer aplikacji) jak i Java SE(standardowa aplikacja). PU (jeden lub więcej) definiuje się w pliku persistence.xml (jest to jedyny wymagany deskryptor w specyfikacji JavaPersistence).
Plik JAR lub katalog zawierajacy META-INF/persistence.xml nazywany jest rootem PU. Może nim być:
- plik EJB-JAR lub application client JAR
- katalog WEB-INF/classes w pliku WAR
- plik JAR w katalogu WEB-INF/lib pliku WAR lub w katalogu bibliotecznym pliku EAR
PersistenceUnit musi mieć nazwę (choć pusty string jest ok) i może być przypisany tylko do jednej bazy.
Definiująć PU można określić transaction-type: JTA(domyślne dla środowisk JEE) lub RESOURCE_LOCAL(domyślne dla środowisk SE). W przypadku RESOURE_LOCAL transakcje będą definiowane ręcznie, za pomocą EntityTransaction API. Definiujemy wówczas odpowiednio jta-data-source lub non-jta-data-source.
Zbiór klas wchodzących w skład PU zostaje określony następująco:
- wszystkie klasy z adnotacją @Entity w JARze (chyba że użyjemy
- zbiór encji ze wskazanych jarów (poprzez
- klasy zmapowane w META-INF/orm.xml (jeżeli taki plik istnieje)
- klasy zmapowane w XML-ach wskazanych przez
- klasy wymienione w elementach
Przykład:
Uzyskiwanie EntityManagera
Możemy go wstrzyknąć poprzez adnotację @PersistenceContext(unitName="sampleUnit") lub utworzyć poprzez EntityManagerFactory.createEntityManager() (wówczas będzie miał zasięg rozszerzony - zasięg transakcji otrzymamy tylko poprzez wstrzyknięcie).Do StatefullSessionBeana można wstrzyknąć EM z zasięgiem rozszerzonym:
Utworzonego ręcznie EM należy ręcznie zamknąć (em.close()). Próba zamknięcie wstrzykniętego EM zakończy się IllegalStateException.
EntityManagerFactory można wstrzyknąć adnotacją @PersistenceUnit(unitName="sampleUnit"). Jeżeli jest ona JTA-Enabled, aby zsynchronizować wprowadzane zmiany z bazą, należy wywołać metodę EntityManager.joinTransaction().
EM wystawia kilka metod:
- em.find(X.class, xPrimaryKey) - ładuje obiekt z bazy. Jak wywołana w ramach transakcji, podpina obiekt pod EM (czyni go zarządzanym). Zwraca nulla, gdy obiekt nie istnieje.
- em.getReference(X.class, xPrimaryKey) - jak wyżej, z tym że zamiast nulla rzuca EntityNotFoundException.
- em.contains(xInstance) - sprawdza, czy obiekt jest zarządzany
- em.persist(xInstance) - wstawia obiekt do bazy (insert), niekoniecznie natychmiast (zależy to od flushMode)
- em.flush() - wymusza natychmiastowe zrzucenie zmian do bazy
- em.merge(xInstance) - przyjmuje odpiętą (detached) encję i zwraca jej podpięty odpowiednik. argument nie ulega zmianie.
- em.remove(xInstance) - usuwa encję z bazy
- em.refresh(xInstance) - nadpisuje encję danymi z bazy
- em.clear() - odpina wszystkie encje od PC
- em.unwrap(), em.getDelegate - zwraca obiekt dostawcy, kryjący się za EM. Pozwala na wykorzystanie rozszerzeń specyficznych dla konkretnego dostawcy.
Wiele z nich wypluje IllegalArgumentException, gdy parametr nie jest encją oraz TransactionRequiredException, jeżeli wywołane poza transakcją na PC o zasięgu transakcyjnym.
FlushMode
Domyślnie flushMode przyjmuje wartość AUTO - tzn. synchronizacja z bazą odbywa się tuż przed wykonaniem powiązanego zapytania oraz tuż przed zatwierdzeniem transakcji. Synchronizacja nie jest wykonywana przed find() ani getReference(). Można zmienić na FlushModeType.COMMIT - wówczas zmiany będą synchronizowane dopiero przed commitem.
Encja potrzebuje 2 metadanych @Entity i @Id. @Entity ma atrybut name - jest to nazwa, po której odwołujemy się do encji w JPQL i domyślnie jest to nazwa klasy. Poprzez @Id określamy klucz główny. PersistenceProvider zakłada, że wszystkie właściwości (properties) w klasie są mapowane na kolumny o tych samych nazwach i typie, a sama klasa na tabelę o tej samej nazwie.
Adnotację @Id można umieścić na polu lub na getterze. Wybór jest istotny i określa, w jaki sposób będą przebiegać odwołania do pól (Property/Field AccessType). Umieszczenie pozostałych adnotacji musi być spójne - albo na polach, albo na getterach/setterach.
Podstawowe adnotacje na encjach (oraz ich atrybuty):
- @Table: name(nazwa tabeli), catalog, schema, uniqueConstraints (przydatne, gdy chcemy wygenerować schemat bazy na podstawie encji)
- @Column: name(nazwa kolumny), unique, nullable, insertable, updatable(można ustawić na false dla kolumn wypełnianych triggerami), columnDefinition(dla określenia konkretnego DDL przy generowaniu schemaut), table(dla mapowań opierających się o kilka tabel), length, precision, scale
- @Transient - pole będzie ignorowane przy operacjach na bazie
- @Basic - domyślne mapowanie na polach. Możemy je dodać, jeżeli chcemy określić atrybuty fetch(default EAGER) i optional(default true). FetchType może zostać zmieniony na LAZY, jednak uwaga - jest to tylko wskazówka. Atrybut optional jest mapowany na nullable przy generacji schematu.
- @Lob - PErsistenceManager potraktuje takie pole jako blob (jeżeli jest typu byte[], Byte[] lub Serializable) lub clob (dla char[], Character[] lub String)
- @Temporal - dla pól typu @Date lub @Calendar. Określa typ pola jako TemporalType.DATE, TIME lub TIMESTAMP, np.: @Temporal(TemporalType.DATE)
- @Enumerated - EnumType.ORDINAL(default) lub STRING. Określa, czy enumy mają być zapisywane w bazie jako ich wartość numeryczna, czy jako wartość zwracana przez toString(). Przykład: @Enumerated(EnumType.STRING)
Klucze główne
Adnotacja @Id określa klucz główny dla encji. Klucz główny musi być prymitywem, wrapperem, Stringiem lub klasą PK składającą się z pól o takich typach. Wartości można generować i wstawiać ręcznie lub pozwolić, by zajął się tym PersistenceProvider - dodając adnotację @GeneratedValue obok @Id. Enum GenerationType ma wartości: TABLE, SEQUENCE, IDENTITY, AUTO(domyślna).
- Table:
Adnotacja @TableGenerator wskazuje tabelę przeznaczonado przechowywania aktualnych wartości "sekwencerów". W powyższym przykładzie, tabela ma kolumny PRIMARY_KEY_KOLUMN(nazwa klucza, dla którego generujemy wartości) oraz VALUE_COLUMN(aktuwalna wartość "sekwencera"). AtrybutpkColumnValue określa nazwę konkretnego klucza (wartość z kolumny PRIMARY_KEY_COLUMN).
- Sequence:
@SequenceGenerator wskazuje na rzeczywistego sekwencera (np. oraclowego).
- Identity - baza sama wie, jaki id nadać
Klasy PK
Klasa reprezentująca klucz główny musi być serializowalna, mieć bezargumentowy konstruktor i zaimplementowane equals i hashCode. Wówczas na klasie encji, obok @Entity dajemy adnotację @IdClass(SamplePKClass.class), a na polach encji odpowiadającym polom z tej klasy dajemy adnotację @Id.
Innuym rozwiązaniem jest stworzenie klasy zaadnotowanej jako @Embeddable. Jej pola mogą być zaadnotowane jako @Column. Wówczas we właściwej encji klucz główny będzie instancją takiej klasy. Zamiast @Id użyjemy na nim adnotacji @EmbeddedId:
W samej encji można przesłonić mapowania kolumn z klasy @Embedded poprzez adnotację @AttributeOverrides:
@Embedded
Niektóre pola encji mogą być złożonymi obiektami zaadnotowanymi jako @Embeddable. Ich mapowania można przesłonić poprzez @AttributeOverrides (opisaną wyżej). Jeżeli pole jest obiektem złożonym i nie ma adnotacji @Embeddable - zostanie potraktowany jak lob i zapisany jako strumień bajtów.
Brak komentarzy:
Prześlij komentarz