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 e rs o n -firstN a m e -la stN a m e
C u s to m e r -stre e t -city -sta te -zip
E m p lo y e e -e m p lo ye e Id
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 @Table(name="PERSON_HIERARCHY") name="PERSON_HIERARCHY") @Inheritance(strategy @Inheritance(strategy= strategy=InheritanceType.SINGLE_TABLE) InheritanceType.SINGLE_TABLE) @DiscriminatorColumn( DiscriminatorColumn(name="DISCRIMINATOR", name="DISCRIMINATOR", discriminatorType= discriminatorType=DiscriminatorType.STRING) DiscriminatorType.STRING) @DiscriminatorValue("PERSON") DiscriminatorValue("PERSON") public class Person implements java. java.io. io.Serializable { private int id; private String firstName; firstName; private String lastName; lastName; @Id @GeneratedValue @GeneratedValue public int getId() getId() { return id; } public void setId( setId(int id) { this.id this.id = id; } public String getFirstName() getFirstName() { return firstName; firstName; } public void setFirstName( setFirstName(String first) first) { this. this.firstName = first; first; } public String getLastName() getLastName() { return lastName; lastName; } public void setLastName( setLastName(String last) last) { this. this.lastName = last; last; } }
Poznámky Anotace pro strategii hierarchie
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("CUST") DiscriminatorValue("CUST") public class Customer extends Person { private String street; street; private String city; private String state; state; private String zip; public String getStreet() getStreet() { return street; street; } public void setStreet( setStreet(String street) street) { this. this.street = street; street; } . . . } // použ použití ití defó defóltní ltní hodnoty diskriminá diskriminátoru @Entity public class Employee extends Customer { private int employeeId; employeeId; public int getEmployeeId() getEmployeeId() { return employeeId; employeeId; } public void setEmployeeId( setEmployeeId(int id) { employeeId = id; } }
Poznámky Jedinými metadaty specifikujícími dědičnost je hodnota diskriminátoru, pokud chcete jinou než defóltní hodnotu.
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, null, firstName varchar(255), varchar(255), lastName varchar(255) varchar(255) ); create table Customer ( id integer primary key not null, null, firstName varchar(255), varchar(255), lastName varchar(255) varchar(255) street varchar(255), varchar(255), city varchar(255), varchar(255), state varchar(255), varchar(255), zip varchar(255) varchar(255) ); create table Employee ( id integer primary key not null, null, firstName varchar(255), varchar(255), lastName varchar(255) varchar(255) street varchar(255), varchar(255), city varchar(255), varchar(255), state varchar(255), varchar(255), zip varchar(255) varchar(255) employeeId integer );
Poznámky 2. Tabulka pro konkrétní třídu Každá tabulka má své vlastnosti a všechny vlastnosti svých nadtříd.
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 @Inheritance(strategy= strategy=InheritanceType.TABLE_PER_CLASS) InheritanceType.TABLE_PER_CLASS) public class Person implements java. java.io. io.Serializable { . . . } @Entity public class Customer extends Person { . . . } @Entity public class Employee extends Customer { . . . }
Metadata jsou uvedena pouze ve tř třídě Person. Person.
Poznámky Mapování této strategie s využitím anotace:
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, null, first name varchar(255), varchar(255), lastName varchar(255) varchar(255) ); create table Customer ( id integer primary key not null, null, street varchar(255), varchar(255), city varchar(255), varchar(255), state varchar(255), varchar(255), zip varchar(255) varchar(255) ); create table Employee ( EMP_PK integer primary key not null, null, employeeId integer );
Poznámky
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 { . . . } @Entity @PrimaryKeyJoinColumn(name="EMP_PK") PrimaryKeyJoinColumn(name="EMP_PK") public class Employee extends Customer { . . . }
Poznámky Anotace pro mapování
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 java. .io. io.Serializable { @Id @GeneratedValue @GeneratedValue public int getId() getId() { return id; } public void setId( setId(int id) { this.id this.id = id; } public String getFirstName() getFirstName() { return firstName; firstName; } public void setFirstName( setFirstName(String first) first) { this. this.firstName = first; first; } public String getLastName() getLastName() { return lastName; lastName; } public void setLastName( setLastName(String last) last) { this. this.lastName = last; 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=@Column(name= Column(name=“SURNAME“ SURNAME“)) public class Customer extends Person { . . . } @Entity @Table(name= Table(name=“EMPLOYEE“ EMPLOYEE“) @PrimaryKeyJoinColumn(name="EMP_PK") PrimaryKeyJoinColumn(name="EMP_PK") public class Employee extends Customer { . . . }
Poznámky
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), ); create table EMPLOYEE ( EMP_PK integer primary key not null, employeeId integer );
Poznámky
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( postInsert(Object entity) { System. System.out. out.println( println(“Inserted entity: “ + entity.getClass entity.getClass(). getClass().getName ().getName()); getName()); } @PostLoad void postLoad( postLoad(Object entity) { System. System.out. out.println( println(“Loaded entity: “ + entity.getClass entity.getClass(). getClass().getName ().getName()); getName()); } }
Poznámky
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
<entity class=" class="com ="com.titan. com.titan.domain .titan.domain. domain.Cabin"> Cabin"> <entity<entity-listeners> listeners> <entity<entity-listener class=" class="com ="com.titan. com.titan.listeners .titan.listeners. listeners.TitanAuditLogger"> TitanAuditLogger"> " listener>" <entity<entity-listener class=" class="com ="com.titan. com.titan.listeners .titan.listeners. listeners.EntityJmxNotifier"> EntityJmxNotifier"> <prepre-persist name=" name="beforeInsert ="beforeInsert"/> beforeInsert"/> <post<post-load name=" name="afterLoading ="afterLoading"/> afterLoading"/> listener> listeners>
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:
Poznámky 1 2 3 4 5 6 7 8 9
EntityManager em = factory. factory.createEntityManager(); createEntityManager(); em. em.getTransaction(). getTransaction().begin ().begin(); begin(); Cabin cabin = new Cabin(); Cabin(); em. em.persist( persist(cabin); cabin); Cabin anotherCabin = em. em.find( find(Cabin. Cabin.class, class, 5); em. em.getTransaction(). getTransaction().commit ().commit(); commit();
Třída EntityJmxNotifier se zají zajímá o metodu zpě zpětné tného volá volání <preEntityJmxNotifier.beforeInsert() beforeInsert() pre-persist>. ersist>. Proto je metoda EntityJmxNotifier.beforeInsert vykoná vykonána hned př při vyvolá vyvolání metody em.persist em.persist( persist(cabin) cabin) v řádku 5. V řádku 7 jsou vyvolá vyvolány metody v ná následují sledujícím poř pořadí adí: TitanAuditLogger.postLoad TitanAuditLogger.postLoad() postLoad() EntityJmxNotifier.afterLoading EntityJmxNotifier.afterLoading() afterLoading() Cabin. Cabin.afterLoading() afterLoading() po té té co je vykoná vykonána metoda EntityManager.find EntityManager.find(). find(). Sprá Správce perzistence zdrž zdrží vklá vkládání entity cabin dokud není není transakce ukonč ukončena (commit (commit). commit).
Pořadí vykonávání entitních posluchačů je pořadí, ve kterém byly v anotace @EntityListeners nebo v XML mapovacím souboru.
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
<entity<entity-mappings> mappings> <entity<entity-listeners> listeners> <entity<entity-listener class=" class="com ="com.titan. com.titan.listeners .titan.listeners. listeners.TitanAuditLogger"> TitanAuditLogger"> <post<post-persist name=" name="afterInsert ="afterInsert"/> afterInsert"/> <post<post-load name=" name="afterLoading ="afterLoading"/> afterLoading"/> listener> <entity<entity-listener class=" class="com ="com.titan. com.titan.listenersEntityJmxNotifier .titan.listenersEntityJmxNotifier"/> listenersEntityJmxNotifier"/> listeners> mappings>
Poznámky
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