niedziela, 28 października 2012

EJB 3.1 [6]

Entity callbacks and listeners

You can define callback for entity lifecycle events in two ways:
1. In entity class you define no-arg, void, checkedExceptionless method and annotate it with one of:
@[Pre/Post][Persist/Update/Remove] or @PostLoad.
2. You define this method in another class (MyFancyListener), except this time it would take and Object argument (representing the entity). The class needs a public, no-arg constructor. Next, you apply listener on entity by @EntityListeners annotation:


In the mapping xml you can define default listeners for all entities:


Then, you can exlude them for a specific entity with @ExcludeDefaultListeners.
Entity inherits parent entity interceptors, but those can also be excluded with @ExcludeSuperclassListeners.

Listeners execute from outside - first default ones, then parent and then those defined for an entity itself.


Security

While creating InitialContext to connect to EJB, you can pass properties containing username (Context.SECURITY_PRINCIPAL) and password (Context.SECURITY_CREDENTIALS).
You can use annotations like @RolesAllowed({"EMPLOYEE"}) and @PermitAll on methods.
Roles are defined either in deployment descriptor () or on the bean (@javax.annotation.security.DeclareRoles({"EMPLOYEE", "CLIENT"))).
Access can be restricted programatically by EJBContext methods getCallerPrincipal() and isCallerInRole().
There is also @RunAs annotation. It can be useful e.g. when MDB needs to access restricted method. (There is no user in context for MDBs).


JNDI and ENC

Beginning with eversion 3.1, all stateless and statefull beans have to be available in global JNDI context under name( elements in '[]' are optional):
java:global[/appOrEARName]/JARorWARName/beanName [!fullInterfaceName]
Example:


Beans have EnterpriseNamingContext. It's something like a map with references to other beans and resources. Before looking up a bean, you need to register it in current bean ENC (to register more EJBs use @EJBs annotation):

and then feel free to grab it:

Notice that when using ejbContext (as opposed to InitialContext) you can omit "java:comp/env" part of JNDI name.
You don't have to register anything if you use @EJB annotation directly on a field or it's setter - the ENC will get populated automatically.
If you specify name attribute of @EJB, the reference will be bound under this name. Otherwise, the name defaults to fullBeanName/fieldName.
Knowing this name, you can override it in xml, like this:

Other than name, @EJB has also mappedName and lookup attributes. MappedName is JNDI name used by provider. Lookup is the same (just vendor independent) - it came with the JNDI name standarization in version 3.1.

EJB name (name attribute of @Statefull/@Stateless or <ejb-name> in xml) has to be unique in EJB-JAR. In EARs there can be duplicates - if this is the case, use #:

Same applies to @PersistenceUnit injection:


When we use @EJB annotation without both mappedName and lookup, the container will:
- Look for an EJB with matching interface in EJB-JAR. if it finds more than one, a deployment exception will be thrown.
- Search other EJB-JARs.
- Search other global EJB-JAR deployments.

Other than EJBs, you can register resources in ENC:

Resources that are Strings or primitives are treated as environmental entries.

wtorek, 16 października 2012

EJB 3.1 [4] - Persistence

Interfejs Query:
- getResultList(); getSingleResult();
- executeUpdate();
- get/setMaxResults(int maxResults);
- get/setFirstResult(int startPosition);
- setHint(); getHints();
- getParameter/s(); getParameterValue();
- setParameter(); //na 50 sposobów
- get/setFlushMode();
- get/setLockType();
- T unwrap(Class);

Interfejs EntityManager:
- createQuery();
- createNamedQuery();
- createNativeQuery();

W zapytaniach używamy atrybutu name encji (domyślnie nazwa klasy).
Parametry mogą być numeryczne (?1) lub nazwane (:name). Ustawiajac parametry typu Calendar/Date przekazujemy TemporalType.
Przykład zapytania JPQL:
"object" jest opcjonalnym archaizmem, "as" też jest opcjonalne. Można zapisać:

Jeżeli getResultList() wyciąga więcej niż jedną kolumnę lub encję, wyniki w zwracanej liście zostaną zagregowane w tablice Object[]:

Można zrobić niejawnego joina nawigując po polach:

Nie można nawigować po polach klasy, która nie jest encją (czyli która jest zapisana w bazie jako bytestream). Po embedable można.
Można w zapytaniu wywołać konstruktor:

Istnieje (nieintuicyjny) operator IN pozwalający przypisać identyfikator elementom kolekcji

To samo możemy osiągnąć joinem:

Jak chcemy zaciągnąć powiązane encje używamy join fetch:

W części WHERE można użyć operatorów arytmetycznych (ale w części SELECT już nie).
Nie można konkatenować Stringów plusem. Jest do tego funkcja concat().
Można porównywać Stringi poprzez <, <=, choc specyfikacja nie określa, jak będą porównywane. Nie można użyć between na Stringach. Between jest inclusive. Porównywanie dwóch encji porówna je po kluczach głównych. Wartość wyrażenia where null in (1,2) jest nieprzewidywalna. Poniższe zapytanie nie wyciągnie osób, które nie mają adresu:
Poniższe zapytanie zwóci listę z nullami, jeżeli istnieją osoby bez adresu:

Można używać "is empty" oraz "member of".
Wyrażenia regularne: % - dowolna ilość znaków, _ - jeden znak, \ - escapowanie.
Dostępne funkcje: lower, upper, trim(leading, trailing, both),concat, lenght, locate(str, str, start), substring(str, start, length), abs, sqrt, mod,current_date, current_time, current_timestamp
Agregujące: count, max, min, avg, sum
Pole, po którym sortujemy (order by) musi należeć do wyciąganej encji i znajdować się w części SELECT. Poniższe zapytanie jest błędne:

Można wykonywać operacje masowe: [update where]/[delete from where]. Przedtem warto wykonać em.flush() i em.clear() by nie rozsynchronizować encji.
W encji można zdefiniować nazwane zapytania i mapowania dla zwracanych wyników. Przykłady:

niedziela, 14 października 2012

EJB 3.1 [3] - Persistence

Można wyróżnić 7 typów relacji między encjami (1:1, 1:N, N:1, N:N, 1- i 2-kierunkowe). Relacje dwukierunkowe 1:N i N:1 niczym się nie różną.

- 1:1 [->]
Pracownik ma adres. Tabela EMPLOYEE ma kolumnę ADDRESS_ID.

@JoinColumn: name (kolumna w tabeli EMPLOYEE, na której bazuje relacja), referencedColumnName (kolumna w tabeli ADDRESS, jeżeli jest inna niż klucz główny (musi być unique))
Adnotacja @JoinColumn jest opcjonalna (można ją pominąć, polegając na domyślnych wartościach). Jeżeli łączymy po kilku kolumnach, używamy @JoinColumns.
@OneToOne: targetEntity, cascade, fetch, optional, mappedBy(dla relacji dwukierunkowych), orphanRemoval(default false - określa, czy usunięcie Pracownika powinno sposodować usunięcie Adresu).

- 1:1 [<->]
Pracownik ma komputer. Komputer ma właściciela. COMPUTER ma kolumnę OWNER_ID.

W relacjach dwukierunkowych mamy do czynienia z encją nadrzędną i podrzędną. Encja podrzędna jest właścicielem relacji (owning side) i posiada klucz obcy do encji nadrzędnej (inverse). Strona inverse posiada adnotację z atrybutem mappedBy (wskazującym na pole w klasie właściciela definiujące relację). W tym przypadku właścicielem relacji jest komputer. (Pilnowanie, by relacja była poprawnie określona po obu stronach (computer.setOwner() i employee.setComputer()) jest zadaniem programisty.

- 1:N [->]
Pracownik ma telefony. Tabela PHONE ma kolumnę OWNER_ID. Model wygląda następująco:

Tym razem @JoinColumn umieszczona w klasie Employee wskazuje na kolumnę w tabeli Phone.

- N:1 [->]
Rejs ma przypisany statek. Tabela CRUISE ma kolumnę SHIP_ID.


- 1:N [<->]
Rejs ma rezerwacje. Tabela RESERVATION ma kolumnę CRUISE_ID.

Właścicielem relacji jest Rezerwacja. @ManyToOne nie może mieć atrybuty mappedBy.

- N:N [<->]
Rezerwacje są powiązane z klientami. Tabela łącząca RESERVATION_CUSTOMER ma kolumny RESERVATION_ID oraz CUSTOMER_ID.

Właścicielem jest Rezerwacja i to ona posiada adnotację @JoinTable.

- N:N [->]
Rezerwacje są powiązane z kabinami. Tabela łącząca CABIN_RESERVATION ma kolumny RESERVATION_ID i CABIN_ID.


Poza Collection i Set można też używać List i Map. Używając listy możemy dodać adnotację @OrderBy("name ASC, surname ASC"). Używając mapy określamy, która właściwość klasy ma być użyta jako klucz w mapiepoprzez @MapKey(name="number"). Bez tej adnotacji, jako klucz użyty zostanie klucz główny.
Polegając na domyślnych wartościach, można ograniczyć metadane relacji do jednej z adnotacji @[One/Many]To[One/Many], bez atrybutów.

Dla konkretnej kolumny lub relacji możemy określić fetchType jako EAGER lub LAZY. Pola z FetchType.LAZY zostaną pobrane z bazy dopiero przy próbie odwołania się do ich wartości (uwaga - fetchType to tylko wskazówka!). Jeżeli pierwsze odwołanie będzie miało miejsce w momencie, gdy encja jest odpięta od EM (detached), większość dostawców wyrzuci pewien rodzaj LazyInitializationException (choć specyfikacja tego nie określa). Rozwiązaniem jest znawigowanie do konkretnego pola, póki encja jest wciąż zarządzana lub jawne wyciągnięcie powiązanej encji w zapytaniu (join fetch).

Operacje kaskadowe
Adnotacje @[One/Many]To[One/Many] mają atrybut cascade, przyjmujący wartości: enum CascadeType{ALL, PERSIST, MERGE, REMOVE, REFRESH} (oraz DETACH od JEE6).
Przykład: @OneToOne(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
CascadeType.PERSIST - przy zapisie pracownika zapisany zostanie również jego adres.
CascadeType.MERGE - przy mergowaniu pracownika, zmergujemy również jego telefony. Jeżeli w międzyczasie dodaliśmy pracownikowi telefon, zostanie on wstawiony do bazy. Jeżeli usunęliśmy jeden z jego telefonów - nie zostanie usunięty (aby uzyskać taki efekt należałoby użyć orphanRemoval).
CascadeType.REMOVE - usuwając pracownika, usuniemy jego adres.
...

Hierarchia encji
Encje mogą tworzyć hierarchię. Przykładowo: Employee extends Customer extends Person. Możemy ją zmapować na 3 sposoby:

1. Tabela na hierarchię (InheritanceType.SINGLE_TABLE)
Jedna tabela zawiera wszystkie dane. Najprostsze, najwydajniejsze, z tym że musimy zrezygnować z constraintów not-null.
W tabeli musimy zdefiniować kolumnę discriminator - określającą, jakim typem osoby jest dany rekord.

- @DiscriminatorColumn - nazwa kolumny rozróżniającej, opcjonalna
- @DiscriminatorValue - wartość ten kolumny dla danej encji, również opcjonalna. Dla typu String jest to domyślnie nazwa klasy. Dla typów char i integer lepiej ją określić (domyślne wartości mogą być mało czytelne).

2. Tabela na konkretną klasę (InheritanceType.TABLE_PER_CLASS)
Każda konkretna klasa ma swoją tabelę. Wiele kolumn jest powielanych w tych tabelach. Mniej wydajne - wymusza łączenie po wielu tabelach w zapytaniach lub użycie union. Z zalet - nie trzeba ograniczać constraintów w bazie.

W encjach dziedziczących nie trzeba już dodawać metadanych związanych z dziedziczeniem. Dla tej strategii domyślny sposób generowania kluczy głównych nie zadziała (można go zmienić np na GenerationType.TABLE).

3. Tabela na subklasę (InheritanceType.JOINED)
Każda klasa ma tabelę i zawierającą kolumny dokładnie odpowiadające jej polom (z pominięciem pól dziedziczonych).
Nie tak wydajna jak SINGLE_TABLE, ale pozwala na constrainty. Bardziej znormalizowana niż TABLE_PER_CLASS.

Tabele w bazie dzielą IDki - tzn. wartości klucza głównego w każdej z tabel odpowiadają sobie. @PrimaryKeyJoinColumn jest opcjonalna, jeżeli nazwy kolumn z kluczem głównym w każdej tabeli są takie same.

W hierarchii dziedziczenia mogą znajdować się klasy nie będące encjami - oznaczamy je jako @MappedSuperclass. Mapowania można przesłonić poprzez @AttributeOverride. Niezadnotowane klasy są ignorowane przez PP.

Encja może być zmapowana na 2 tabele poprzez @SecondaryTable:

Jeżeli tych tabel jest więcej , używamy @SecondaryTables:

sobota, 13 października 2012

EJB 3.1 [2] - Persistence

Encje to klasy z adnotacją @javax.persistence.Entity (lub odpowiednikiem xml-owym). Mogą byc zarządzane (managed) lub odłączone (detached).

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.