beziehungen persistente domänenmodelle mit jpa 2.0 und bean validation
Post on 05-Apr-2015
108 Views
Preview:
TRANSCRIPT
Beziehungen
Persistente Domänenmodelle mit JPA 2.0 und Bean Validation
Eine Parent-Child Beziehung
Wie sieht das Klassen-Modell aus? Employee hat eine Referenz auf Department Department hat eine Collection von Employee
Referenzen Beides: Bidirektionale Beziehung
Unabhängig davon ist das zugrundeliegende DB-Schema:
Eine Parent-Child Beziehung
Mapping des Klassenmodells auf das DB-Schema mittels JPA: Metadata ist erforderlich. Je nach Klassenmodell wird entweder eine
many-to-one Beziehung oder eine one-to-many Beziehung gemappt
Falls beide Richtungen gemappt werden sollen, so muss definiert werden, dass für beide derselbe Foreign-Key zugrunde liegt.
Eine Parent-Child Beziehung@Entitypublic class Employee { … @ManyToOne private Department department; …
@Entitypublic class Department { … @OneToMany @JoinColumn (name=”department_id”) private Set<Employee> employees =
new HashSet<Employee>(); …
@Entitypublic class Department { … @OneToMany (mappedBy = “department“) private Set<Order> employees =
new HashSet<Employee>(); …
Mapping der one-to-many Beziehung• Field/Property muss ein Interface sein• Achtung:– Unidirektionales one-to-many ohne
Beziehungstabelle wird erst ab JPA2 unterstützt
Mapping der many-to-one Beziehung
Mapping der bidirektionalen Beziehung
• JPA muss wissen, dass nur ein Foreign-Key für beide Richtungen existiert.
Collection Types
• Richtung
– Unidirektional
– Bidirektional
• Kardinalität
– One-to-one
– Many-to-one
– One-to-many
– many-to-many
Employee Project
Employee Address
Source Target
Employee Department* 1
Employee Project* *
Employee Address1 1
Employee Phone1 *
one-to-one, unidirektional
Employee
@OneToOneprivate Address address;
entspricht:
@OneToOne@JoinColumn(name="address_id", referencedColumnName = "id")private Address address;
(JPA2 unterstützt auch one-to-one mit einer zusätzlichen Zwischentabelle)
-id : int-name : String-salary : long
Employee
-id : int-street : String-city : String-state : String-zip : String
Address
0..1
ADDRESS
ID
CITY (O) STATE (O) STREET (O) ZIP (O)
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
many-to-one, unidirektional
-id : int-name : String-salary : long
Employee
-id : int-name : String
Department
* 0..1
DEPARTMENT
ID
NAME (O)
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
DEPARTMENT
ID
NAME (O)
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
DEPARTMENT
ID
NAME (O)
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
Employee@ManyToOneprivate Department department;
(JPA2 unterstützt auch many-to-one mit einer zusätzlichen Zwischentabelle)
one-to-many, bidirektional
Phone
@ManyToOne(optional = false)private Employee employee;
Employee
@OneToMany(mappedBy = "employee")private Collection<Phone> phones;
-id : int-phonenumber : String-type : String
Phone
-id : int-name : String-salary : long
Employee
* 1
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
PHONE
ID
PHONENUMBER (O) TYPE (O) EMPLOYEE_ID (O) (FK)
many-to-many, bidirektional
Employee
@ManyToMany(mappedBy = "employees")
private Collection<Project> projects;
Project
@ManyToMany
private Collection<Employee> employees;
-id : int-name : String-salary : long
Employee
-id : int-name : String
Project
* *
PROJECT
ID
DTYPE (O) NAME (O)
PROJECT_EMPLOYEE
PROJECTS_ID (FK)EMPLOYEES_ID (FK)
EMPLOYEE
ID
NAME (O) SALARY (O) STARTDATE (O) DEPARTMENT_ID (O) (FK)MANAGER_ID (O) (FK)ADDRESS_ID (O) (FK)
Many-To-Many Beziehungen
„If you think that two objects share a simple many-to-many relationship, you haven't looked closely enough at the domain. There is a third object waiting to be discovered with attributes and a life cycle all its own.“ - Dierk König
• Oft sind weitere Daten auf der Zwischentabelle nötig
• Üblicherweise mappt man dann die Zwischentabelle auf eine eigene Entity
one-to-many, unidirektional• Bei einer unidirektionalen one-to-many Beziehungen
fehlt das mappedBy Element und das Target hat keine Rückbeziehung
• JPA verwendet in diesen Fällen ebenfalls eine Beziehungstabelle
• JPA 2 spezifiziert die unidirektionale one-to-many Beziehung ohne Zwischentabelle.
@OneToMany@JoinColumn (name=”department_id”)private Set<Employee> employees = new HashSet<Employee>();
@OneToManyprivate Set<Employee> employees = new HashSet<Employee>();
Bidirektionale Beziehungen
JPA verändert die Java-Semantik nicht!
D.h. der korrekte Unterhalt von bidirektionalen Beziehungen ist Sache der Applikation!
Department taxes = new Department();
Employee john = new Employee();
taxes.getEmployees().add(john);
john.setDepartment(taxes);
Bidirektionale Beziehungen
Best Practice: Convenience Methoden auf den Entities:@Entitypublic class Department { … @OneToMany private List<Employee> employees = new ArrayList<Employee>();
public void addEmployee(Employee employee){ if (employee == null)
throw new IllegalArgumentException(“Null employee“); if (employee.getDepartment() != null) employee.getDepartment().getEmployees().remove(employee); getEmployees().add(employee); employee.setDepartment(this); } …}
Analog: removeEmployee() sowie Methoden auf Employee.
Verwendung von Collections• java.util.Set
– Eindeutig (Object.equals())– @OneToManyprivate Set<Phone> phones;
• java.util.List– geordnet, kann sortiert werden– @OneToMany@OrderBy("phonenumber ASC")private List<Phone> phones;
• java.util.Map– Key/Value Paare– @OneToMany@MapKey(name = "phonenumber")private Map<String, Phone> phones;
JPA 2:Persistenter Index@OneToMany@OrderColumn(name="index")private List<Phone> phones;
Lazy- und Eager-Loading
• Default bei one-to-one und many-to-one– FetchType.EAGER
• Default bei one-to-many und many-to-many– FetchType.LAZY
• Defaultverhalten kann übersteuert werden. z.B.– @OneToMany(fetch = FetchType.EAGER)private Set<Phone> phones;
EntityManager em = ...Department foreignAffairs = em.find(Department.class, 123);foreignAffairs.getEmployees().iterator().next();
Beziehungen werden transparent (nach)geladen:
Speichern und Löschen von BeziehungenDepartment taxes = new Department();Employee john = new Employee();taxes.addEmployee(john);Employee jane = new Employee();taxes.addEmployee(jane);
em.persist(taxes);em.persist(john);em.persist(jane);em.flush();
for (Employee empl : taxes.getEmployees()){ em.remove(empl);}em.remove(taxes);em.flush();
• Jede Entity hat einen eigenen, unabhängigen Lifecycle!• IllegalStateException wenn vergessen wird, eine assoziierte Entity zu persistieren.
• Delete all entities individually
Transitive Persistenz
Persistenz wird von JPA propagiert auf assoziierte Entities.
@OneToMany (mappedBy = “department“, cascade = CascadeType.ALL)
private Set<Employee> employees = new HashSet<Employee>();
Department taxes = new Department();Employee john = new Employee();taxes.addEmployee(john);Employee jane = new Employee();taxes.addEmployee(jane);
em.persist(taxes);em.flush();
em.delete(taxes);em.flush();
• Kaskadierung wird auf der Assoziation konfiguriert
• Speichern eines Parents speichert auch alle Kinder
• Löschen eines Parents löscht auch alle Kinder.
CascadeType {ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH};
Orphan DeletionPhone phone1 = …Employee john = em.find(Employee.class, 123);john.getPhones().remove(phone1);em.flush();
• Child wird nicht gelöscht!
Entfernen eines Kindes aus der Collection des Parents setzt nur den Foreign Key auf der Kind-Tabelle auf NULL.– FK Constraint Verletzung möglich– Das Kind ist nun “orphaned”
• In JPA 1 muss das Löschen von Orphans explizit in der Applikation erfolgen.• JPA 2 unterstützt das automatische Löschen von Orphans
@OneToMany(cascade=ALL, mappedBy=”customer”, orphanRemoval=true) public Set<Order> getOrders() { return orders; }
top related