lundi 29 octobre 2012

Java Serveur App 8 : JPA et les relations

Relations

Une entité peut contenir des champs entier, des chaines de caractères ou encore des dates. 

Maintenant, nous allons aussi vouloir permettre à une entité de faire référence à une autre entité. C'est là qu'interviennent les relations.

OneToMany et ManyToOne

Un restaurant a plusieurs plats. Chaque plat n'appartient qu'à ce restaurant. On a donc une relation de 1 restaurant vers plusieurs plats. Dans l'autre sens de cette relation, plusieurs plats sont reliés à un restaurant. Cela nous mène au code suivant:



package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;


@Entity
public class Restaurant implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String name;

 //bi-directional many-to-one association to FoodItem
 @OneToMany(fetch = FetchType.LAZY, cascade={CascadeType.ALL}, orphanRemoval = true)
 private List<FoodItem> foodItems;

    public Restaurant() {
    }

 public int getId() {
  return this.id;
 }

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

 public String getName() {
  return this.name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public List<FoodItem> getFoodItems() {
  return this.foodItems;
 }

 public void setFoodItems(List<FoodItem> foodItems) {
  this.foodItems = foodItems;
 }
 
}
Le champs qui représente la relation est foodItems qui contiendra la liste des plats du restaurant.

Le code d'un plat (FoodItem) contient également une référence vers le restaurant auquel ce plat appartient. C'est ce qu'on appellera une relation bidirectionnelle.

package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;

import com.restooo.model.Basket;
import com.restooo.model.Restaurant;

@Entity
public class FoodItem implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String name;

 private String description;

 private Double price;

 //bi-directional many-to-many association to Basket
 @ManyToMany(mappedBy="foodItems")
 private List<Basket> baskets;

 //bi-directional many-to-one association to Restaurant
    @ManyToOne
 private Restaurant restaurant;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public FoodItem() {
 }

 public int getId() {
  return this.id;
 }

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

 public String getDescription() {
  return this.description;
 }

 public void setDescription(String description) {
  this.description = description;
 }

 public Double getPrice() {
  return price;
 }

 public void setPrice(Double price) {
  this.price = price;
 }

 public List<Basket> getBaskets() {
  return baskets;
 }

 public void setBaskets(List<Basket> baskets) {
  this.baskets = baskets;
 }

 public Restaurant getRestaurant() {
  return restaurant;
 }

 public void setRestaurant(Restaurant restaurant) {
  this.restaurant = restaurant;
 }

}



ManyToMany

Une commande contient plusieurs plats en quantité différentes. Nous allons appeler notre commande Basket (Order est un mot réservé SQL). Nous y voyons le côté principal est celui de la commande à laquelle on ajoutera des plats se déclare par le champs foodItems. 


package com.deguet.model;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;


/**
 * The persistent class for the Basket database table.
 * 
 */
@Entity
public class Basket implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;

 private String test;
 
 //bi-directional many-to-many association to FoodItem Owning SIDE
 @ManyToMany
    @JoinTable(
           name="basket_fooditem",
           joinColumns={@JoinColumn(name="BASKET_ID", referencedColumnName="id")},
           inverseJoinColumns={@JoinColumn(name="FOODITEM_ID", referencedColumnName="id")})
 private List<FoodItem> foodItems;

    public Basket() {
    }

 public int getId() {
  return this.id;
 }

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

 public List<FoodItem> getFoodItems() {
  return this.foodItems;
 }

 public void setFoodItems(List<FoodItem> foodItems) {
  this.foodItems = foodItems;
 }

 public String getTest() {
  return test;
 }

 public void setTest(String test) {
  this.test = test;
 }
 
}

Lazy fetch

Les relations et les méthodes getXXX correspondantes permettent une méthode de chargement retardée appelée Lazy Loading en anglais. L'objet relié n'est chargé que quand la méthode getXXX est appelée.


Exemple: un restaurant avec 350 plats est chargé en mémoire. La requête récupère juste le restaurant. Ensuite, si on exécute un getFoodItems() sur le champ contenant les plats, une autre requête est faite à la base de données.


Création d'un restaurant et de ses plats

Pour illustrer la création d'entité ainsi que d'autres entités liées, nous allons créer un restaurant avec plusieurs centaines de plats à travers notre code. Pour cela nous allons créer une classe InitialLoad avec le code suivant:

package com.deguet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import com.deguet.model.FoodItem;
import com.deguet.model.Restaurant;

public class InitialLoad {

 
 private static Facade<Restaurant> rf = new Facade<Restaurant>(Restaurant.class);
 
 public static void load(){
  String[] types = {"Pizza ","Sushi ","Patate ","Tacos ","Pita ","Cassoulet ","Paela ","Quiche "};
  String[] noms  = {"Réjean ","Paulo ","Joris ","Claudette ","Jean ","Paul ","Pierre ","Pedro ","Bertha ","Johnny "};
  String[] plats = {"du jour ","fancy ","italiana ","québécoise ","pour trois","à la mode de chez nous",
    "espanola","qui tue ", "qui pique","pas cher","pour deux","pour toute la famille","du Colonel Sanders"};
  Random rand = new Random();
  for (String type : types){
   for (String nom : noms){
    Restaurant r = new Restaurant();
    List<FoodItem> dishes = new ArrayList<FoodItem>();
    r.setName(type+nom);
    r.setFoodItems(dishes);
    rf.create(r);
    Collections.shuffle(Arrays.asList(plats));
    for (int i = 0 ; i < 5 ; i++)
    {
     String plat = plats[i];
     FoodItem item = new FoodItem();
     item.setRestaurant(r);
     item.setDescription("Bla ingrédients ...");
     item.setName(type+plat);
     item.setPrice((rand.nextInt(100)+50)/10.0);
     dishes.add(item);
    }
    rf.edit(r);
   }
  }
 }

}


persistence.xml

Si vous oubliez de mentionner les nouvelles classes à JPA dans votre persistence.xml, vous aurez un message d'erreur "bli.bla.Blo  is not a known entity type".


Fichiers et lecture supplémentaire

Vous trouverez ici le zip du projet.

Une lecture supplémentaire fortement recommandée est le wikibook sur JPA.

1 commentaire: