sábado, 23 de abril de 2011

JPA I - Manejo de relaciones

Este tutorial pretende explicar como se mapean las entidades en JPA y sus relaciones. 
Al final de este tutorial, el lector encontrara un link para descargar los ejemplos vistos en todos los tutoriales JPA de este blog.

Tomamos como ejemplo el siguiente modelo de negocio



La entidad Customer manteniene una referencia a una entidad Referee mediante la anotación @OneToOne. Esta relacion es de tipo uno-a-uno (one-to-one) unidireccional ya que UNA entidad Customer tiene UNA referencia a Referee mientras que la entidad Referee no mantiene ninguna referencia a Customer.
En dicho caso, la entidad Customer es el dueño de la relacion de manera implicita, y por tanto cada entidad de este tipo contendra por defecto una columna adicional en la base de datos que sera utilizada para acceder al objeto Referee. A esta columna se la conoce con el nombre de clave foranea (FK).
Para personalizar/customizar la columna que contendra la clave foranea podemos usar la anotación @JoinColumn

La entidad Customer manteniene una referencia a la entidad Account mediante la anotación @OneToMany. Esta relacion es de tipo 1 a N bidireccional ya que UNA entidad Customer  tiene N referencias a Accounts y UNA entidad Account tiene UNA referencia a la entidad Customer.
En este caso, en vez de utilizar una clave foranea en Customer esta calve sera almacenada en la tabla Account.
En caso de que la relacion sea unidireccional, JPA utilizara por defecto una tabla de union (join table).que permite que ambos lados de la asociacion tengan una clave foranea que es almacenada en una tercera tabla con dos columnas. Como en el caso anterior, se podra customizar esta relacion mediante la anotacion @JoinColumn
Un tema a tener en cuenta para este tipo de relaciones: 
http://www.coderanch.com/t/218942/ORM/java/JPA-Hibernate-OneToMany-without-join

 
La entidad Customer mantiene una referencia a la entidad Address mediante la anotacion @ManyToMany. Esta relacion es de tipo N a N bidireccional ya que UNA entidad Customer tiene N referencias a Address y UNA entidad Address tiene N referencias a la entidad Customer. En estos casos, en lugar de tener una clave foranea o bien en customer o bien en address, JPA va a crear una tercer tabla que contrendra. Ahora bien, en este tipo de relaciones (de la misma manera q ocurre en las relaciones 1 a 1 bidireccionales) se tendra que elegir arbitrariamente cual de los dos extremos de la relacion sera el dueño de dicha relacion para que JPA pueda realizar correctamente el mapeo.
Como en el caso anterior, se podra customizar esta relacion mediante la anotacion @JoinColumn
 
Para el caso de las relaciones bidireccionales, ambos extremos de la relacion mantienen una referencia al otro. Por ende, el dueño de la relacion debe ser especificado explicitamente para que JPA pueda realizar el mapeo de manera adecuada.Para esto se le agrega el atributo mappedBy a la anotacion de relacion que tengamos. Ej. @OneToOne(mappedBy = "marido").
El atributo mappedBy podra ser utilizado en las relaciones@OneToOne, @OneToMany y @ManyToMany. Solamente el cuarto tipo de relacion, @ManyToOne no acepta este atributo




@Entity
public class Customer implements Serializable{
   
    @Id
    @Column(name="CUSTOMER_ID")
    private int id;
   
    @Column(name="FIRST_NAME")
    private String firstName;
   
    @Column(name="LAST_NAME")
    private String lastName;
   
    @OneToOne
    private Referee referee;
   
    @OneToMany(mappedBy="customer", fetch=FetchType.EAGER)
    private List accounts;
   
    @ManyToMany
    // NOTA: Esta annotation no es necesaria, pero si sirve para
    //       dejar mas prolija la entidad que relaciona a las tablas customer y
    //       address
    @JoinTable(name="CUSTOMER_ADDRESS",
               joinColumns=@JoinColumn(name="CUST_ID",
                                          referencedColumnName="CUSTOMER_ID"
                                         ),
               inverseJoinColumns=@JoinColumn(name="ADD_ID",
                                                 referencedColumnName="ADDRESS_ID")
    )
    private List addresses;  

    @Column(name="GENDER", length=20)
    @Enumerated(EnumType.STRING)
    private Gender gender;

    public Customer() {
   
    }

    getters and setters....
}


A continuacion, explicamos las anotaciones de la entidad Customer:

@Id denota a la propiedad como PK

@Column(name="xxx")  sobreescrible el nombre por default que JPA le asignaria a dicha columna

@OneToMany(mappedBy="customer", fetch=FetchType.EAGER) Relacion 1 a N bidireccional  entre Customer y Account. En estos casos JPA define que el extremo N sera el dueño de la relacion, por ende, alguna propiedad de la entidad Account sera el dueño (sera contra la cual se debera realizar el mappedBy="xxx". Para ello, en la entidad Account tendremos una propiedad llamada customer que tendra la referencia inversa a Customer. De esta forma queda establecida la relacion 1 a N bidireccional. En caso que no querramos que sea bidireccional, no sera necesario agregar el atributo mappedBy ni tampoco sera necesario agregar la propiedad customer en la entidada Account. Dicho de otra manera, Customer solo tendra como referencia a Account mientras q Accounts no conocera a Customer.

Las asociaciones/relaciones son afectadas por el concepto de lectura temprana  (eager) o demorada (lazy). El tipo de lectura por defecto para las relaciones uno-a-uno y muchos-a-uno es temprana (eager). El tipo de lectura para las dos clases de relaciones restantes, uno-a-muchos y muchos-a-muchos es demorada (lazy). Por supuesto, estos valores pueden ser cambiados como en este ejemplo. Vemos que tenemos una relacion 1 a N y el tipo de lectura que le seteamos es EAGER.


@ManyToMany
@JoinTable(name="CUSTOMER_ADDRESS",
                      joinColumns=@JoinColumn(name="CUST_ID",
                                                                      referencedColumnName="CUSTOMER_ID"
                                                                     ),
                      inverseJoinColumns=@JoinColumn(name="ADD_ID",
                                                                                   referencedColumnName="ADDRESS_ID"
                                                                                  )
                    )
Relacion N a N bidireccional  entre Customer y Address. En estos casos JPA deja que uno pueda definir cual sera el extremo que sera dueño de la relacion. En este ejemplo, se opto porque Customer sea el dueño de la relacion y Address sea la relacion inversa. Por lo tanto en la entidad Address tendremos el atributo mappedBy en la relacion. Por defaul, JPA crea una tercer tabla para este proposito con valores por defecto, pero en este ejemplo vemos como se customizan dichos valores para que esta tercer tabla quede con nombres mas acordes y prolijos. Esta customizacion la logramos mediante la anotacion @JoinTable
Empezamos con @JoinTable(name="CUSTOMER_ADDRESS mediante el cual definimos explicitamente le nombre de esta tercer tabla.
Luego joinColumns=@JoinColumn(name="CUST_ID",                                                                      referencedColumnName="CUSTOMER_ID"), lo cual nos permite modificar el nombre de la columna FK de esta tercer tabla llamandola CUST_ID que hara referencia a la columna PK de la tabla dueña de la relacion, osea de Customer, como bien lo indica el atributo referencedColumnName.
Y por ultimo, tenemos inverseJoinColumns=@JoinColumn(name="ADD_ID",                               referencedColumnName="ADDRESS_ID" ), lo cual nos permite modificar el nombre de la columna FK de esta tercer tabla llamandola ADD_ID que hara referencia a la columna PK de la tabla que que pertence a la relacion inversa, osea de Address, como bien lo indica el atributo referencedColumnName.





@Entity
public class Account implements Serializable{
   
    @Id
    @Column(name="ACCOUNT_ID")
    private int id;
   
    private double balance;
   
    @ManyToOne
    @JoinColumn(name="CUSOMTER_ID",
                referencedColumnName="CUSTOMER_ID")
    // NOTA: mediante el joinColumn explicitamos que el nombre de la FK de esta tabla
    //         (ACCOUNT) se llamara CUSTOMER_ID y la PK de la tabla CUSTOMER a
    //         la cual hara referencia esta FK se llama CUSTOMER_ID (segun el @column de Customer)           
    private Customer customer;


    public Account() {

    }

    getters and setters....
   
  }


A continuacion, explicamos las anotaciones de la entidad Account:

@Id denota a la propiedad como PK

@Column(name="xxx")  sobreescrible el nombre por default que JPA le asignaria a dicha columna

@ManyToOne @JoinColumn(name="CUSTOMER_ID",                
                                                   referencedColumnName="CUSTOMER_ID"
                                                  )
Relacion N a 1 bidireccional entre Account y Customer. En estos casos JPA define que el extremo N sera el dueño de la relacion, por ende, la propiedad customer de la entidad Account sera el dueño de dicha relacion 

Mediante la anotacion @JoinColumn explicitamos (no dejamos que JPA lo defina por nosotros) que el nombre de la FK de la tabla ACCOUNT sera CUSTOMER_ID y que hara referencia a la PK de la tabla CUSTOMER llamada CUSTOMER_ID




@Entity
public class Address implements Serializable{

    @Id
    @Column(name="ADDRESS_ID")
    private int id;
   
    private String addressLine;
   
    private String country;
   
    private String postCode;
   
    @ManyToMany(mappedBy="addresses")
    private List customers;
   
    public Address() {
       
    }


    getters and setters....

}


A continuacion, explicamos las anotaciones de la entidad Address:

@Id denota a la propiedad como PK

@Column(name="xxx")  sobreescrible el nombre por default que JPA le asignaria a dicha columna

@ManyToMany(mappedBy="addresses")  Relacion N a N bidireccional  entre Adderess y Customer. En estos tipos de relaciones N a N, JPA deja que uno pueda definir cual sera el extremo que sera dueño de la relacion. y \como ya se vio anteriorimente en la entidad Customer, se opto porque Customer sea el dueño de la relacion y Address sea la relacion inversa. Para ello, en la entidad Customer tendremos una propiedad llamada addresses que tendra la referencia a Address. De esta forma queda establecida la relacion N a N bidireccional.




@Entity
public class Referee implements Serializable{

    @Id
    private int id;
    private String name;
    private String comments;
   
    public Referee() {
       
    }

    getters and setters....

}


A continuacion, explicamos las anotaciones de la entidad Referee:

@Id denota a la propiedad como PK

Como esta relacion entre Referee y Customer es del tipo 1 n 1 unidireccional, la entidad Referee no mantiene ninguna referencia a Customer.




Descargas de ejemplos de mapeos JPA y consultas JPQL

Por favor, haga clic en: Descargar ejemplos JPA


Bibliografia

* http://es.scribd.com/doc/46308101/Introduccion-a-JPA

* http://schuchert.wikispaces.com/JPA+Tutorial+1+-+Getting+Started

* EJB 3 Developer Guide - A Practical Guide for developers and architects to the Enterprise Java Beans
   Standard - Michael Sikora - 2008


5 comentarios:

  1. Muy buena explicación sobre este tema de la relaciones en JPA...
    Espero el resto de los temas a tratar...
    Gracias

    ResponderEliminar
  2. Muchas gracias por tus palabras.. Si, la idea es seguir completando el curso de JPA..

    ResponderEliminar
  3. muy util tu explicacion, espero nuevos temas

    ResponderEliminar
  4. Heeeeeeeeeeeey Macarena, ahhhi.
    Enhorabuena. Muy buen tutorial.

    ResponderEliminar
  5. hay algo que no entiendo en la relación ManyToMany, por ejemplo tengo Empleados><Proyecto y los empleados ya estan en la base de datos, si agrego un proyecto agregandole los empleados recuperados de la bd, me sale un error de que la primary key debe ser unica, podrian explicar como hacer la persistencia de ello?

    ResponderEliminar