DPKOM_06 Dědičnost entit a zpětná volání posluchači 1
Obsah přednášky Jedna tabulka pro hierarchii tříd Tabulka pro konkrétní třídu Tabulka pro podtřídu Neentitní základní třídy Události zpětného volání Entitní třídy zpětného volání Posluchači entit (Entity Listeners) 2
Způsoby O/R mapování Specifikace Java Persistence poskytuje tři různé způsoby, jak mapovat hierarchii dědičnosti do relační databáze: 1. Jedna tabulka pro hierarchii tříd Jedna tabulka bude mít všechny vlastnosti (atributy) každé třídy v hierarchii. P erson -firstn am e -lastn am e C ustom er -street -city -sta te -zip E m p loyee -em ployeeid 3
Způsoby O/R mapování 2. Tabulka pro konkrétní třídu Každá třída bude mít pro ni deklarovanou tabulku, se všemi jejími vlastnostmi a vlastnostmi její nadtřídy mapované do této tabulky. 3. Tabulka pro podtřídu Každá třída bude mít svoji vlastní tabulku. Každá tabulka bude mít vlastnosti, které jsou definované v té konkrétní třídě. Tyto tabulky nemají žádné vlastnosti svých nadtříd nebo podtříd. 4
1. Jedna tabulka pro hierarchii tříd Do jednoduché tabulky jsou mapovány všechny třídy. To znamená, že datové atributy tříd Person, Customer a Empoloyee jsou jako vlastnosti v jedné tabulce: create table PERSON_HIERARCHY ( id integer primary key not null, firtsname varchat(255), lastname varchar(255), street varchar(255), city varchar(255), state varchar(255), zip vatchar(255), employeeid integer, DISCRIMINATOR varchar(31) not null, //identif. typu uložené entity ); 5
1. Jedna tabulka pro hierarchii tříd Speciální sloupec DISCRIMINATOR identifikuje typ entity, který je uložen v dané tabulce. 6
@Entity @Table(name name="person_hierarchy") @Inheritance(strategy strategy=inheritancetype InheritanceType.SINGLE_TABLE) @DiscriminatorColumn DiscriminatorColumn(name name="discriminator", discriminatortype=discriminatortype DiscriminatorType.STRING) @DiscriminatorValue DiscriminatorValue("PERSON") public class Person implements java.io io.serializable { private int id; private String firstname; private String lastname; Poznámky Anotace pro strategii hierarchie @Id @GeneratedValue @ public int getid() { return id; } public void setid(int id) { this.id = id; } public String getfirstname() { return firstname; } public void setfirstname(string first) { this.firstname = first; } } public String getlastname() { return lastname; } public void setlastname(string last) { this.lastname = last; }
1. Jedna tabulka pro hierarchii tříd Pro definování perzistentní strategie pro relaci dědičnosti se používá anotace @javax.persistance.inheritance: package javax.persistence; @Target(TYPE) @Retention(RUNTIME) public @interface Inheritance { } InheritanceType strategy() default SINGLE_TABLE; public enum InteritanceType { } SINGLE_TABLE, JOINED, TABLE_PER_CLASS 8
1. Jedna tabulka pro hierarchii tříd Atribut strategy() definuje použité mapování instance. Anotace @Inheritance musí být umístěna v kořenu hierarchie třídy, pokud neměníte strategii mapování v podtřídě. package javax.persistence; @Target(TYPE) @Retention(RUNTIME) public @interface DiscriminationColumn String name() deafult DTYPE ; DiscriminationType discriminationtype() default STRING; String columndefinition() default ; int length() default 10; } 9
1. Jedna tabulka pro hierarchii tříd Sloupec Discriminator určuje instance, které třídy tabulka reprezentuje. Atribut name() určuje jméno sloupce a dicsriminatortype() specifikuje typ diskriminačního sloupce. Typem může být STRING, CHAR nebo INTEGER. Implicitní je STRING. package javax.persistence; @Target(TYPE) @Retention(RUNTIME) public @interface DiscriminatorValue { String value(); } 10
1. Jedna tabulka pro hierarchii tříd Anotace @javax.persistence.discriminatorvalue definuje, jakou hodnotu bude mít diskriminační sloupec pro řádek, ve kterém je uložena instance třídy Person. Pokud zůstane atribut nedefinovaný, správce perzistence vygeneruje hodnotu automaticky. 11
@Entity @DiscriminatorValue DiscriminatorValue("CUST") public class Customer extends Person { private String street; private String city; private String state; private String zip; public String getstreet() { return street; } public void setstreet(string String street) { this.street street = street; }... } Poznámky Jedinými metadaty specifikujícími dědičnost je hodnota diskriminátoru, pokud chcete jinou než defóltní hodnotu. // použit ití defóltn ltní hodnoty diskriminátoru @Entity public class Employee extends Customer { private int employeeid; } public int getemployeeid() { return employeeid; } public void setemployeeid(int id) { employeeid = id; }
1. Jedna tabulka pro hierarchii tříd V uvedeném příkladu entita Customer nastavuje hodnotu sloupce diskriminátor na CUST s využitím anotace @DiscriminatorValue. U entity Employee je hodnota sloupce diskriminátor nastavena defóltně na Employee. 13
Výhody Mapování SINGLE_TABLE je nejjednodušší na implementaci a provádění. Pracuje se pouze s jednou tabulkou. Správce perzistence nemusí provádět žádné složité spojování sjednocení nebo výběry, když nahrává entity, nebo když traverzuje polymorfické relace, protože všechna data jsou uložena v jedné tabulce. 14
Nevýhody Jedna velká nevýhoda tohoto přístupu je, že všechny sloupce vlastností podtříd musí být nastavitelné na null. Pokud potřebujete nějakou vlastnost nastavit na NOT NULL je to problém, protože to nejde. Protože sloupce vlastností podtříd nemusí být využity, není uvedená strategie normalizovatelná. 15
create table Person ( id integer primary key not null, firstname varchar(255), lastname varchar(255) ); create table Customer ( id integer primary key not null, firstname varchar(255), lastname varchar(255) street varchar(255), city varchar(255), state varchar(255), zip varchar(255) ); Poznámky 2. Tabulka pro konkrétní třídu Každá tabulka má své vlastnosti a všechny vlastnosti svých nadtříd. create table Employee ( id integer primary key not null, firstname varchar(255), lastname varchar(255) street varchar(255), city varchar(255), state varchar(255), zip varchar(255) employeeid integer );
2. Tabulka pro konkrétní třídu Jedna hlavní odlišnost mezi danou strategií a strategií SINGLE_TABLE je, že není potřebný žádný sloupec diskriminátor v databázovém schématu. 17
@Entity @Inheritance(strategy strategy=inheritancetype InheritanceType.TABLE_PER_CLASS) public class Person implements java.io io.serializable {... } @Entity public class Customer extends Person {... } Poznámky Mapování této strategie s využitím anotace: @Entity public class Employee extends Customer {... } Metadata jsou uvedena pouze ve třídě t Person.
Výhody Výhoda tohoto přístupu oproti SINGLE_TABLE je v tom, že je možné definovat omezení (NOT NULL) na vlastnostech (atributech) podtříd. 19
Nevýhody Tato strategie není normalizovaná, protože má nadbytečné (redundantní) sloupce. Tato strategie může být implementovaná tak, že kontejner používá vícenásobné dotazy při nahrávání entit nebo polymorfických relací. To je pro kontejner hodně náročné. Jiný způsob implementace je, že kontejner používá SQL UNION operace. Je to rychlejší než předchozí implementace vícenásobných výběrů. 20
Nevýhody Problémem tohoto způsobu implementace může dále být, že některé databáze nepodporují uvedený rys SQL. 21
3. Tabulka pro podtřídu V této strategii mapování má každá podtřída svoji vlastní tabulku, ale tato tabulka obsahuje pouze vlastnosti, které jsou definované v konkrétní třídě. Stručněřečeno, tato strategie je podobná strategii TABLE_PER_CLASS s výjimkou toho, že schéma je normalizované. Také někdy nese označení JOINED strategie: 22
create table Person ( id integer primary key not null, first name varchar(255), lastname varchar(255) ); Poznámky create table Customer ( id integer primary key not null, street varchar(255), city varchar(255), state varchar(255), zip varchar(255) ); create table Employee ( EMP_PK integer primary key not null, employeeid integer );
3. Tabulka pro podtřídu Když správce perzistence nahrává entitu (load), která je podtřídou, nebo traverzuje polymorfické relace, používá SQL join operaci na všechny tabulky hierarchie. Při mapování musí být v každé tabulce sloupec, který se využívá při spojování každé tabulky. V našem příkladě tabulky EMPLOYEE, CUSTOMER a PERSON sdílí hodnotu stejného primárního klíče. 24
@Entity @Inheritance(strategy Inheritance(strategy=InheritanceType.JOINED InheritanceType.JOINED) public class Person implements java.io.serializable {... } @Entity @DiscriminatorValue("CUST DiscriminatorValue("CUST") ") public class Customer extends Person {... } Poznámky Anotace pro mapování @Entity @PrimaryKeyJoinColumn(name PrimaryKeyJoinColumn(name="EMP_PK") public class Employee extends Customer {... }
3. Tabulka pro podtřídu Správce perzistence potřebuje vědět, který sloupec v každé tabulce bude použit pro spojování, při nahrávání entity. K tomu se používá anotace @javax.persistence.primarykeyjoincolumn: package javax.persistence; @Target({TYPE, METHOD< FIELD}) public @interfacfe PrimaryKeyJoinColumn } String name() default ; String referencedcolumnname() default ; String columndefinition() default ; 26
3. Tabulka pro podtřídu Atribut name() odkazuje na sloupec, kterým budete provádět operaci join. Je obsažen v dané tabulce. Defóltně odkazuje na primární klíč nadtřídy. Sloupec referencedcolumnname() bude použit k provádění operace join z tabulky nadtřídy. Může to být libovolný sloupec tabulky nadtřídy, standardně je to primární klíč. Jsou-li jména primárních klíčů mezi danou třídou a podtřídou identická, pak není třeba specifikovat anotaci (Customer, Person). 27
3. Tabulka pro podtřídu Pro složený klíč se využije anotace @javax.persistence.primarykeyjoincolumns: package javax.persistence; @Target({TYPE, METHOD, FIELD}) public @interface PrimaryKeyJoinColumns { @PrimaryKeyJoinColumns[] values(); } 28
Výhody Není tak rychlé jako strategie SIMPLE_TABLE, je ale možné definovat omezení NOT NULL a model je normalizovatelný. Tato strategie je lepší než strategie TABLE_PER_CLASS ze dvou důvodů: 1. Model relační databáze se dá kompletně normalizovat. 2. Pracuje lépe, pokud databáze nepodporuje SQL UNIONS operace. 29
Nevýhody Nepracuje tak rychle jako strategie SINGLE_TABLE. 30
Neentitní základní třída Někdy potřebujete zdědit z neentitní nadtřídy. Tato nadtřída může být existující třída ve vašem doménovém modelu, ze které nechcete vytvářet entitu. Anotace @javax.persistencemappedsuperclass dovoluje definovat tento typ mapování: 31
@MappedSuperclass public class Person implements java.io io.serializable { @Id @GeneratedValue @ public int getid() { return id; } public void setid(int id) { this.id = id; } Poznámky public String getfirstname() { return firstname; } public void setfirstname(string first) { this.firstname = first; } public String getlastname() { return lastname; } public void setlastname(string last) { this.lastname = last; } } @Entity @Table(name Table(name= CUSTOMER CUSTOMER ) @Inheritance(strategy Inheritance(strategy=InheritanceType.JOINED InheritanceType.JOINED) @AttributeOverride(name AttributeOverride(name= lastname lastname, column=@column(name Column(name= SURNAME SURNAME )) public class Customer extends Person {... } @Entity @Table(name Table(name= EMPLOYEE EMPLOYEE ) @PrimaryKeyJoinColumn(name PrimaryKeyJoinColumn(name="EMP_PK") public class Employee extends Customer {... }
Neentitní základní třída Protože to není entita, mapovaná nadtřída nemá asociovanou tabulku. Každá podtřída zdědí perzistentní vlastnosti nadtřídy (base class). Jakékoli mapované vlastností mapované třídy lze zastínit (override) použitím anotace @javax.persistence.attributeoverride. Následuje databázové schéma modifikované hierarchie: 33
create table CUSTOMER ( id integer primary key not null, firstname varchar(255), SURNAME varchar(255), street varchar(255), city varchar(255), state varchar(255), zip varchar(255), ); Poznámky create table EMPLOYEE ( EMP_PK integer primary key not null, employeeid integer );
Neentitní základní třída Entita Customer zdědila vlastnosti: id, firstname a lastname. Protože bylo specifikováno @AttributeOverride, sloupec pro lastname bude SURNAME. Tato mapovací strategie je užitečná, když chcete mít nadtřídu pro entitu a nechcete ji donutit, aby byla sama entitou. 35
Zpětná volání entity a posluchači Entity Callbacks and Listeners Když EntityManager vykonává metody jako např. persist(), merge(), find() a remove(), nebo libovolný dotaz EJB QL, je spuštěna předdefinovaná množina událostí životního cyklu. Např. metoda persist() spouští databázový inserts, metoda merge() spouští updaty v databázi apod. Někdy je velmi užitečné, aby třída entitního beanu byla informovaná o tom, že nastaly uvedené události (spustily se dodatečné metody). 36
Zpětná volání entity a posluchači Entity Callbacks and Listeners Specifikace Javy Persistence dovoluje vytvořit metody zpětného volání (callback methods) ve třídě beanu tak, aby entitní instance byly informovány, že tyto události nastaly. Je možné také registrovat odděleného posluchače, který bude očekávat ty stejné události. 37
Události zpětného volání Callback Events Daná anotace reprezentuje každou fázi životního cyklu entity: @javax.persistence.prepersist @javax.persistence.postersist @javax.persistence.postload @javax.persistence.preupdate @javax.persistence.postupdate @javax.persistence.preremove @javax.persistence.postremove Událost @PrePersist nastane ihned, když entitymanager vyvolá metodu persist(), nebo kdykoli entitní instance je naplánovaná na vložení do databáze (jako kaskádní merge). 38
Události zpětného volání Callback Events Podobně událost @PostPersist není spuštěna, dokud není dokončeno vložení do databáze. 39
Zpětné volání v entitních třídách (Callback on Entity Classes) Instance entitního beanu může být registrovaná pro metodu zpětného volání anotací třídy beanu pomocí metody bez argumentů, která vrací void a vyhazuje nekontrolovanou výjimku. } @Entity public class Cabin {... @PostPersist void afterinsert() {... } @PostLoad void afterloading() {... } Nastane-li odpovídající událost, entity manager vyvolá odpovídající anotovanou metodu. 40
Posluchači entity Entity Listeners Posluchači entit jsou třídy, které mohou genericky zachytit události zpětného volání. Nejsou to entitní třídy jako takové, ale mohou být připojeny ke třídě entit prostřednictvím vazby anotace nebo XML. Ve třídě entitního posluchače můžete označit metody, které zachytí konkrétní událost životního cyklu. Tyto metody vrací void a mají jeden Object jako parametr, což je instance entity, na které nastane událost. 41
public class TitanAuditLogger { @PostPersist void postinsert(object entity) { System.out out.println println( Inserted entity: + entity.getclass getclass(). ().getname getname()); } Poznámky } @PostLoad void postload(object entity) { System.out out.println println( Loaded entity: + entity.getclass getclass(). ().getname getname()); }
Posluchači entity Entity Listeners Třída posluchače entity musí mít veřejný konstruktor bez argumentů. Ten může být aplikován na entitní třídu s využitím anotace @javax.persistence.entitylisteners: package javax.persistence; @Target(Type) @Retention(RUNTIME) public @interface EntityListeners { Class[ ] value(); } 43
Posluchači entity Entity Listeners V entitní třídě je možné specifikovat jeden nebo více posluchačů: @Entity @EntityListeners({TitanAuditLogger.class, EntityJmxNotifier.class}) public class Cabin {... @PostPersist void afterinsert() {... } @PostLoad void afterloading() {... } } Použitím anotace @EntityListeners na entitu Cabin způsobí že, jakákoli metoda zpětného volání uvnitř entitních posluchačů bude vyvolána, kdykoli instance entity Cabin bude v interakci s perzistentním kontextem. 44
Poznámky Anotace zabezpečí vyvolání metod zpětného volání kdykoli instance třídy Cabin bude v interakci s perzistentním kontextem. Tvar XML: <entity <entity <entity <entity class class class class=" =" =" ="com com com com.titan..titan..titan..titan.domain domain domain domain.cabin Cabin Cabin Cabin"> "> "> "> <entity <entity <entity <entity-listeners listeners listeners listeners> <entity <entity <entity <entity-listener listener listener listener class class class class=" =" =" ="com com com com.titan..titan..titan..titan.listeners listeners listeners listeners.titanauditlogger TitanAuditLogger TitanAuditLogger TitanAuditLogger"> "> "> "> </entity </entity </entity </entity-listener listener listener listener>" >" >" >" <entity <entity <entity <entity-listener listener listener listener class class class class=" =" =" ="com com com com.titan..titan..titan..titan.listeners listeners listeners listeners.entityjmxnotifier EntityJmxNotifier EntityJmxNotifier EntityJmxNotifier"> "> "> "> <pre pre pre pre-persist persist persist persist name name name name=" =" =" ="beforeinsert beforeinsert beforeinsert beforeinsert"/> "/> "/> "/> <post <post <post <post-load load load load name name name name=" =" =" ="afterloading afterloading afterloading afterloading"/> "/> "/> "/> </entity </entity </entity </entity-listener listener listener listener> </entity </entity </entity </entity-listeners listeners listeners listeners> </entity> </entity> </entity> </entity>
1 EntityManager em = factory.createentitymanager createentitymanager(); 2 em.gettransaction gettransaction(). ().begin begin(); 3 4 Cabin cabin = new Cabin(); 5 em.persist persist(cabin cabin); 6 7 Cabin anothercabin = em.find find(cabin Cabin.class class, 5); 8 9 em.gettransaction gettransaction(). ().commit commit(); Poznámky Pořadí vykonávání entitních posluchačů je pořadí, ve kterém byly v anotace @EntityListeners nebo v XML mapovacím souboru. Třída EntityJmxNotifier se zajímá o metodu zpětn tného volání <pre pre-persist ersist>. Proto je metoda EntityJmxNotifier.beforeInsert beforeinsert() vykonána na hned při vyvolání metody em.persist persist(cabin cabin) v řádku 5. V řádku 7 jsou vyvolány metody v následuj sledujícím pořad adí: TitanAuditLogger.postLoad postload() EntityJmxNotifier.afterLoading afterloading() Cabin.afterLoading afterloading() po té co je vykonána na metoda EntityManager.find find(). Správce perzistence zdrží vkládání entity cabin dokud není transakce ukončena (commit commit).
Defóltní entitní posluchači Je možné specifikovat množinu defóltních entitních posluchačů, kteří jsou aplikováni na každou entitní třídu v perzistentní jednotce s využitím elementu <entity-listeners>. Např. aplikace TitanAuditLogger v každé entitní třídě vyžaduje uvést v konkrétní perzistentní jednotce následující: 47
Poznámky <entity <entity <entity <entity-mappings mappings mappings mappings> <entity <entity <entity <entity-listeners listeners listeners listeners> <entity <entity <entity <entity-listener listener listener listener class class class class=" =" =" ="com com com com.titan..titan..titan..titan.listeners listeners listeners listeners.titanauditlogger TitanAuditLogger TitanAuditLogger TitanAuditLogger"> "> "> "> <post <post <post <post-persist persist persist persist name name name name=" =" =" ="afterinsert afterinsert afterinsert afterinsert"/> "/> "/> "/> <post <post <post <post-load load load load name name name name=" =" =" ="afterloading afterloading afterloading afterloading"/> "/> "/> "/> </entity </entity </entity </entity-listener listener listener listener> <entity <entity <entity <entity-listener listener listener listener class class class class=" =" =" ="com com com com.titan..titan..titan..titan.listenersentityjmxnotifier listenersentityjmxnotifier listenersentityjmxnotifier listenersentityjmxnotifier"/> "/> "/> "/> </entity </entity </entity </entity-listeners listeners listeners listeners> </entity </entity </entity </entity-mappings mappings mappings mappings>
Defóltní entitní posluchači Pokud potřebujete vypnout defóltní entitní posluchače na konkrétní entitě, použijete k tomu anotaci @javax.persistence.excludedefaultlisteners: @Entity @ExcludeDefaultListeners public class Cabin {... } 49
Posluchači a dědičnost Pokud máte hierarchii dědičnosti entit, ve které má základní třída (nadtřída) aplikované entitní posluchače, každá podtřída může zdědit tyto entitní posluchače. Pokud má podtřída aplikované své entitní posluchače, potom budou připojeni jak posluchači základní (bázové) třídy, tak i posluchači podtřídy. 50