Introductie

During this part of the training we will instruct using JPA

  1. OneToMany

  2. ManyToOne

  3. ManyToMany

Definitions

Some definitions
  • Unidirectional vs bidirectional

  • OneToMany

  • ManyToOne

  • ManyToMany

@OneToOne

Unidirectional

Suppose an Employee always has a single address and we have to model it in jpa

Then the following JAVA code (BLUE) will be created by you, the programmer, and Database (RED) will be created then automagically by JPA

ObjectRelational OneToOne
Figure 1. Blue is Java Code, Red is Database

So we create the entity Addresss

package nl.programit.people.domain;
... imports omitted

@Entity
public class Address {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private long id;

        private String street;
        private int number;

        public long getId() {
                return id;
        }

        public void setId(long id) {
                this.id = id;
        }

        public String getStreet() {
                return street;
        }

        public void setStreet(String street) {
                this.street = street;
        }

        public int getNumber() {
                return number;
        }

        public void setNumber(int number) {
                this.number = number;
        }

}

Example code

@Entity
public class Employee {

  @Id
  private long id;


  // fails with the exception that JPA does not know what type of column to create for Address? Clear??
  private Address address;

  public void setAddress(Address address) {
                this.address = address;
        }
}

Fix the problem above with the @OneToOne annotation so JPA knows that it has to make a mapping to another table called a relationship / foreign key

@Entity
public class Employee {

  @Id
  private long id;


  @OneToOne
  private Address address;

 public void setAddress(Address address) {
                this.address = address;
        }
}

Using the least work principe that is all we have to do to create the relationship

Under the hood JPA does the following work for us
  • it creates the table Address

  • it creates an address_id column in the table Person

Now we have create a so called unidirectional relationship which means we can only traverse from the Person to the Address

The address table / entity does not have any clue to which person it belongs!!!

Bidirectional

Suppose we want to tell JPA also to wire the direction from Address to Person Than we have to add this to the JPA Address entity

  // fetch = do we get all addressen when fetching a Person or do we fetch them when we "dive" into them ...
  /// mappedBy = the other / owning side his property (in this case address since that is the owning side of Person)
 @OneToOne(fetch=FetchType.LAZY, mappedBy="address")
  private Person person;

  // add getters and setters

Question: what happens to the tables in the people app?

Answer …​ after a minute of discussing? Nothing …​ why ? :-)

Since if jpa has an adress entity he can found the person by finding a person with an adress_id which is the samen as the id of this address instance.

@OneToMany

Unidirectional

Suppose a person has many phones AND a phone belongs to one person

ObjectRelational ManyToOne

We than use the @OneToMany annotation

@OneToMany
private Set<Phone> phones = new HashSet<>();


public void addPhone(Phone phone) {
        this.phones.add(phone);
}

JPA then creates a person_phones table? Why ?

Since the entity Phone does not know that he is even linked to a Person hence JPA creates a so called link table (Dutch: koppel tabel)

Bidirectional

Suppose we want to be able to get the person which own the phone

Add the mappedBy property to the @OneToMany annotation on the owning side

The mappedBy is ALWAYS the type of the Set we are dealing with. Hence in this case it is the Phone class - which has the @ManyToOne - annotation. Just pick the name of the variable and you are done …​ for now :-)

In our Person class (the owning side)
public class Person {

        private String name;
        ...
        ...

        @OneToMany(mappedBy="person") // person is the private Person instance var in the phone class
        private Set<Phone> phones = new HashSet<>();

        public Set<Phone> getPhones() {
                return this.phones;
        }

        public void addPhone(Phone phone) {
                this.phones.add(phone);
        }
}
In our Phone class (the belonging side)
public class Phone {

        private String phoneNumber;
        ...
        ...

        @ManyToOne
        private Person person;
}

@ManyToMany

Introducing jointable

In a ManyToMany relationship we have a so-called many-to-many relationship between two classes.

e.g. A Person can have many hobbies and a hobby can have many Person(s).

JPA will create a so called join-table / link-table (Dutch: koppeltabel) to link the relationship between the two

Introducing Cascading

During this ManyToMany relationship part we also are talking about Cascading. In this case, cascading means …​ what should we do when we delete a Person? Should we also delete his phones in the relationship?

What you should know is that the @ManyToMany annotation has a 'cascade' attribute which you can set.

That cascade attribute should contain an 'CascadeType' enum value which are …​
  • ALL

  • PERSIST

  • MERGE

  • REMOVE

  • REFRESH

  • DETACH

You are telling with this annotation what JPA should do on the action. So if we say …​ cascade=CascadeType.REMOVE on the Person class on the Hobbies set than removing a Person will also remove the links from the linktable after deletion

Unidirectional

Suppose a person has many hobbies and a hobby can belong to multiple persons

GraphExplainingPersonHobbyMappingProgramItYed
Figure 2. class diagram
Add this to the person class
@ManyToMany(cascade=CascadeType.ALL)
private Set<Hobby> hobbies = new HashSet<>();

         public void addHobby(Hobby h) {
        this.hobbies.add(h);
                h.getPeople().add(this);
        }

The machine creates a join table (in this case hobbies_people)

Bidirectional

Suppose want to see al people who like running

Add the following to the Person class (mappedBy)
@ManyToMany(mappedBy="people", cascade=CascadeType.ALL)
private Set<Hobby> hobbies = new HashSet<>();
Add the following to the Hobby class
@ManyToMany(cascade=CascadeType.ALL)
private Set<Person> people = new HashSet<>();

public void addPerson(Person p) {
                 this.people.add(p);
                p.getHobbies().add(this);
}
Never use a mappedBy on both sides of the relationship. Simply only on the owning side
Be aware for the following. Using a getter to get the Set of Hobbies out of person and than adding an item to the Set will eventually fail. You have to add a hobby through the owning class (in this case through Person)
Solution: create a getter and create an addHobby to the Person class
 public void addHobby(Hobby h) {
        this.hobbies.add(h);
                h.getPeople().add(this);
}

FAQ

What is that Cascade thing?

  • When a person is deleted what happens to the hobby?

    • The Cascading defines what should happen to the underlying properties of an entity

    • In fact we have to tell something about assocations and specially regarding aggregations and compositions

Issues you might run into

Recursive rendering of ManyToMany relationships

Symptom
  • When creating REST controller and GETting a result ends in some very large text in Postman and in Spring Boot the console print some kind of recursion error (java.lang.StackoverflowError)

Cause
  • During rendering the machine renders a list with Persons and in each person a List of Hobbies which on it’s turn have a List of Person which on it’s turn have a list of hobbies …​

    • In fact you have a indirect recursion

Fix
  • Add @JsonIgnoreProperties("<some field>") above your field in your domain class which is the start of the problem (and it may be on both ends)

Example

In our example using Person and Hobbies we have the problem when rendering the Hobbies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
        private String name;

        @ManyToMany(...)
        @JsonIgnoreProperties("people"); // <= people is the TO BE IGNORED field in the HOBBY class
        private Set<Hobbies> hobbies = new HashSet<>();
}


public class Hobby {
        private String name;

        @ManyToMany(...)
        @JsonIgnoreProperties("hobbies");
        private Set<Person>) people = new HashSet<>();
}
If you have a lot of @JsonIgnoreProperties you can also add him to the class. The result is the union of all the mentioned properties

DIY

Do it yourself
  • Implement the above changes to your people project

    • Add Address-entity and add the @OneToOne relationship

    • Add Phone-entity and add the @OneToMany relationship

    • Add Hobby-entity and add the @ManyToMany relationship

  • Watch what happens during the starting of the application with your database-structure

More Resources