Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

18
Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli

Transcript of Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

Page 1: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

Objektrelationales Mapping mit JPA

Beziehungen

Jonas BandiSimon Martinelli

Page 2: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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:

Page 3: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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.

Page 4: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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:– Mapping entspricht nicht JPA-Standard! – Unidirektionales one-to-many ohne

Beziehungstabelle wird erst ab JPA2 unterstützt– Jedoch: Konkrete JPA-Implementationen

unterstützen es bereits

Mapping der many-to-one Beziehung

Mapping der bidirektionalen Beziehung

• JPA muss wissen, dass nur ein Foreign-Key für beide Richtungen existiert.

Page 5: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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 *

Page 6: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

one-to-one, unidirektional

Employee

@OneToOneprivate Address address;

entspricht:

@OneToOne@JoinColumn(name="address_id", referencedColumnName = "id")private Address address;

-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)

Page 7: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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;

Page 8: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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)

Page 9: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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)

Page 10: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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

Page 11: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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. Etliche JPA Provider unterstützen dies bereits heute.

@OneToMany@JoinColumn (name=”department_id”)private Set<Employee> employees = new HashSet<Employee>();

@OneToManyprivate Set<Employee> employees = new HashSet<Employee>();

Page 12: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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);

Page 13: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

Bidirektionale BeziehungenBest 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.

Page 14: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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;

Page 15: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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:

Page 16: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

Speichern und Löschen von Beziehungen

Department 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

Page 17: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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};

Page 18: Objektrelationales Mapping mit JPA Beziehungen Jonas Bandi Simon Martinelli.

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”

• JPA kann Orphans nicht automatisch löschen, dies muss explizit in der Applikation erfolgen.

• Proprietäre Mechanismen von JPA-Providers erlauben bereits das automatische Löschen von Orphans

• JPA 2 unterstützt das automatische Löschen von Orphans

• Child wird nicht gelöscht!

@OneToMany(cascade=ALL, mappedBy=”customer”, orphanRemoval=true) public Set<Order> getOrders() { return orders; }