martedì 29 luglio 2014

Hibernate relazione molti-a-molti

Vai all'indice

Una relazione molti-a-molti esiste quando un singolo record in una tabella può essere correlato a molti record in una seconda, e un singolo record nella seconda a molti record nella prima tabella.
Per questo esempio utilizzerò le tabelle utente e applicazione definite nel primo post, da notare che esiste una terza tabella utente_applicazione che mette in collegamento le altre due. Ogni utente può essere collegato a più applicazioni ed ogni applicazione può essere utilizzata da più utenti.



Di seguito modifico l'entità Utente.java dell'esempio precedente per la gestione della relazione molti-a-molti e definisco la nuova entità Applicazione.java.

package jar;

import java.sql.Date;
import java.util.HashSet; 
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.ManyToOne;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.JoinTable;
import javax.persistence.JoinColumns;
import javax.persistence.JoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="utente")
public class Utente {

   @Id
   @GeneratedValue
   @Column(name="userid")
   private int userId;
   
   @Column(name="nome")
   private String nome;
   
   @Column(name="cognome")
   private String cognome;
   
   @Column(name="ruolo")
   private String ruolo;
   
   @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "utente")
    @JoinColumn(name="userid")
   private Dettaglioutente dettaglioutente;
    
    @ManyToOne
    @JoinColumn(name="ufficioId", 
                insertable=false, updatable=false, 
                nullable=false)
    private Ufficio ufficio;
    
    @ManyToMany(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(name="utente_applicazione", 
                joinColumns={@JoinColumn(name="userid")}, 
                inverseJoinColumns={@JoinColumn(name="appid")})
    private Set<Applicazione> applicazioni = new HashSet<Applicazione>();  

   
   public Utente() {
      
   }
   
   public Utente(String nome, String cognome, String ruolo) {
      this.nome = nome;
      this.cognome = cognome;
      this.ruolo = ruolo;
      
   }
   
   // Metodi Getter e Setter
   

   public Set<Applicazione> getApplicazioni() {
      return applicazioni;
   }

   public void setApplicazioni(Set<Applicazione> applicazioni) {
      this.applicazioni = applicazioni;
   }   

   public int getUserId() {
      return userId;
   }

   public void setUserId(int userId) {
      this.userId = userId;
   }


   public String getNome() {
      return nome;
   }

   public void setNome(String nome) {
      this.nome = nome;
   }

   public String getCognome() {
      return cognome;
   }

   public void setCognome(String cognome) {
      this.cognome = cognome;
   }

   public String getRuolo() {
      return ruolo;
   }

   public void setRuolo(String ruolo) {
      this.ruolo = ruolo;
   }

   public Dettaglioutente getDettaglioutente() {
      return dettaglioutente;
   }

   public void setDettaglioutente(Dettaglioutente dettaglioutente) {
      this.dettaglioutente = dettaglioutente;
   }
   
   public Ufficio getUfficio() {
      return ufficio;
   }

   public void setUfficio(Ufficio ufficio) {
      this.ufficio = ufficio;
   }   
}


package jar;

import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name="applicazione")
public class Applicazione {

    @Id
    @Column(name="appid")
    @GeneratedValue
    private int appid;

    @Column(name="nomeapp")
    private String nomeapp;

    @Column(name="desrizioneapp")
    private String descrizioneapp;    
     
    @ManyToMany(fetch = FetchType.LAZY, mappedBy="applicazioni", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<Utente> utenti = new HashSet<Utente>();
    
   public Applicazione() {
      
   }

    public Applicazione(String nomeapp, String descrizioneapp) {
        this.nomeapp = nomeapp;
        this.descrizioneapp = descrizioneapp;
    }

   public int getAppid() {
      return appid;
   }

   public void setAppid(int appid) {
      this.appid = appid;
   }

   public String getNomeapp() {
      return nomeapp;
   }

   public void setNomeapp(String nomeapp) {
      this.nomeapp = nomeapp;
   }

   public String getDescrizioneapp() {
      return descrizioneapp;
   }

   public void setDescrizioneapp(String descrizioneapp) {
      this.descrizioneapp = descrizioneapp;
   }

   public Set<Utente> getUtenti() {
      return utenti;
   }

   public void setUtenti(Set<Utente> utenti) {
      this.utenti = utenti;
   }
   

}

L'annotazione @ManyToMany viene utilizzata per definire una relazione molti-a-molti tra le entità Utente e Applicazione. Ogni associazione molti-a-molti ha una parte proprietaria e una non proprietaria.
L’annotazione @JoinTable  si definisce sul lato proprietario di una relazione molti-a-molti e serve per indicare la tabella che registra le associazioni, che nel nostro caso è Utente_applicazione. Se l'associazione è bidirezionale, entrambe le parti possono essere definite proprietarie.
L’attributo name dell’annotazione @JoinTable specifica la tabella che memorizza i riferimenti alle primary key delle tabelle utente e applicazione.
Questa tabella contiene le colonne userid e appid che sono le chiavi esterne che fanno riferimento alle chiavi primarie delle due tabelle.
Gli elementi joinColumns e inverseJoinColumns fanno riferimento alle primary key; il primo descrive la parte proprietaria della relazione. L’annotazione @JoinColumn  viene utilizzata per definire la colonna di collegamento in entrambe le tabelle.
L’attributo mappedBy serve per indicare l’entità proprietaria della relazione nell’entità subordinata e viene usato esclusivamente nel caso di una relazione bidirezionale.
Il parametro cascade  comunica all’EntityManager come estendere un’operazione di persistenza su una particolare entità verso le entità a questa collegate.
Di default l’elemento cascade è vuoto e quindi le operazioni di persistenza non vengono diffuse.
I valori possono essere settati a ALL, MERGE, PERSIST, REFRESH, REMOVE.
Da notare che ho usato <SET> per mappare la tabella Applicazione su Utente e viceversa. <set> può memorizzare solo oggetti unici, ciò significa che elementi duplicati non possono essere contenuti in un set. Quando si aggiunge lo stesso elemento di un set per la seconda volta, andrà a sostituire quello già esistente. Il tipo corrispondente di un <set> in Java è java.util.Set.

Quindi modifico il file: /src/main/resources/hibernate.cfg.xml mappando jar.Applicazione:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
  <!-- DB connection settings -->
  <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
  <property name="connection.url">jdbc:derby://localhost:1527/derby/derbyDB</property>
  <property name="connection.username">tst</property>
  <property name="connection.password">test</property>

  <property name="connection.pool_size">1</property>
  <property name="dialect">org.hibernate.dialect.DerbyDialect</property>
  <property name="current_session_context_class">thread</property>
  <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
  <property name="show_sql">true</property>
  <property name="hbm2ddl.auto">validate</property>

  <mapping class="jar.Utente"/>
  <mapping class="jar.Dettaglioutente"/>
  <mapping class="jar.Ufficio"/>
  <mapping class="jar.Applicazione"/>

</session-factory>
</hibernate-configuration>

La classe HibernateUtil rimane invariata.
La classe Main.java esegue le operazioni di CRUD (Inserimento, lettura e cancellazione). E' possibile eseguire l'applicazione cliccando col tasto destro su questa classe, quindi selezionare Run as > Java Application. Il risultato dell'elaborazione è visibile nella scheda console di Eclipse.

package jar;

import java.sql.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.HibernateException;

public class Main {

   @SuppressWarnings("unchecked")
   public static void main(String[] args) {

      System.out.println("Hibernate Test");
      
      SessionFactory sf = HibernateUtil.getSessionFactory();

      int iduff = 0;
      
      // Inserimento
      Session session = sf.openSession();
      session.beginTransaction();   
      int delIdapp = 0; //definisco l'id dell'app. che cancellerò
      try {
           Applicazione app1 = new Applicazione("SAP", "Sistema gestionale");
           Applicazione app2 = new Applicazione("GIS", "Sistemi territoriali");
           session.save(app1);
           session.save(app2);
           delIdapp = app1.getAppid();

         Utente ute1 = new Utente("Franco", "Neri", "Utente");
         Utente ute2 = new Utente("Giovanni", "Bianchi", "Admin");   
               
         ute1.getApplicazioni().add(app1);
         ute1.getApplicazioni().add(app2);
         ute2.getApplicazioni().add(app1);
         ute2.getApplicazioni().add(app2);
         session.save(ute1);
         session.save(ute2);

         session.getTransaction().commit();
      }catch (HibernateException e) {
         session.getTransaction().rollback();
           e.printStackTrace();
      } finally {
         session.close();
      }
      
      Lista(); 

      // Cancello una applicazione e tutte le associazioni con gli utenti
      session = sf.openSession();
      session.beginTransaction();         
      try {   
          Applicazione appx = (Applicazione) session.load(Applicazione.class, delIdapp);
          List<Utente> utenti = session.createQuery("from Utente").list();
             for (Utente utex : utenti) {
                utex.getApplicazioni().remove(appx);
                session.save(utex);
             } 
             session.delete(appx);
         
         session.getTransaction().commit();   
      }catch (HibernateException e) {
         session.getTransaction().rollback();
           e.printStackTrace();
      } finally {
          session.close();
      }
       
       Lista();       

   }

   @SuppressWarnings("unchecked")
   public static void Lista() {
      Session session = HibernateUtil.getSessionFactory().openSession();
      session.beginTransaction();   
      System.out.println("Lista utenti ufficio");
      List<Utente> uts = session.createQuery("from Utente").list();
      for (Utente ut1 : uts) {
         System.out.println("Info utente " + ut1.getNome()  + " , "
            + ut1.getCognome() + ", "
            + ut1.getRuolo()); 
            for(Applicazione appy : ut1.getApplicazioni()) {
                    System.out.println("Nome applicazione associate all'utente " + appy.getNomeapp());
                }
              
      }
      session.close();
   }
}

Di seguito riporto tutti i moduli creati finora nel progetto.





Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!