2011-08-30 11 views
7

Ho riscontrato un problema con un'associazione da molti a molti nel mio livello di persistenza. Il mio scenario è il seguente:Hibernate Associazione many-to-many: la raccolta a sinistra contiene elementi, ma la raccolta a destra è vuota

Un utente può avere diversi ruoli e un ruolo può avere diversi utenti collegati ad esso. Durante le prove ho riscontrato uno strano comportamento. Ho creato oggetti ruolo e diversi oggetti utente. Il ruolo è stato impostato su ciascuno degli utenti. Successivamente, gli utenti sono stati salvati utilizzando un DAO. Quindi viene caricato uno degli utenti per verificare se ha ricevuto il ruolo che gli è stato passato prima di salvare l'oggetto utente. Chiamando getRoles() per l'utente viene visualizzato che il ruolo è stato impostato correttamente.

Per verificare se la direzione inversa funziona anche il ruolo oggetto viene caricato dal database utilizzando un ruolo DAO. Ma chiamare getUsers() sull'oggetto ruolo restituisce solo un set vuoto, sebbene debba contenere tutti gli utenti con questo ruolo.

Ho controllato la tabella del database ma tutto sembra a posto. Le tabelle utente, ruolo e user_role sono state tutte compilate correttamente.

Quindi, perché l'oggetto ruolo non contiene alcun utente?

Sto utilizzando Hibernate e Spring con le seguenti classi.

classe User

@Entity 
@Table 
public class User extends BusinessObject { 

    ... 

    // Option 1 
    @ManyToMany(fetch = FetchType.LAZY, 
       cascade = CascadeType.ALL, 
       targetEntity=Role.class) 
    @JoinTable(name= "user_role", 
       joinColumns = {@JoinColumn(name="user_id")}, 
       inverseJoinColumns = {@JoinColumn(name="role_id")}) 

    // Option 2 
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    @JoinTable(name= "user_role", 
        joinColumns = {@JoinColumn(name="user_id")}, 
      inverseJoinColumns = {@JoinColumn(name="role_id")}) 
    private Set<Role> roles = new HashSet<Role>();  

    ... 
} 

classe Ruolo

@Entity 
@Table 
public class Role extends BusinessObject { 
    ... 

    // Option 1 
    @ManyToMany(fetch = FetchType.LAZY, 
       cascade = CascadeType.ALL, 
       mappedBy= "roles", 
       targetEntity = User.class) 

    // Option 2 
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    @JoinTable(name= "user_role", 
        joinColumns = {@JoinColumn(name="role_id")}, 
        inverseJoinColumns = {@JoinColumn(name="user_id")}) 
    private Set<User> users = new HashSet<User>();   

    ... 
} 

Per provare Sto utilizzando il seguente codice in una classe di test JUnit.

@Test 
public void test(){  
    Transaction trans = sessionFactory.getCurrentSession().beginTransaction(); 

    Role userAdminRole = new Role(); 
    userAdminRole.setName(RoleName.USER_ADMIN); 
    Role userRole = new Role(); 
    userRole.setName(RoleName.USER); 

    User user1 = new User(); 
    user1.setEmail("[email protected]");   
    user1.getRoles().add(userAdminRole); 
    user1.getRoles().add(userRole); 
    userDao.save(user1); 

    User user2 = new User(); 
    user2.setEmail("[email protected]"); 
    user2.getRoles().add(role); 
    userDao.save(user2); 

    User user3 = new User(); 
    user3.setEmail("[email protected]"); 
    user3.getRoles().add(role); 
    userDao.save(user3);    

    trans.commit();  

    User loadedUser = userDao.load(user1.getId()); 

      // Tests passes 
    Assert.assertNotNull(loadedUser); 
    Assert.assertEquals(user1, loadedUser); 

    Set<Role> roles = loadedUser.getRoles();   

      // Tests passes 
    Assert.assertEquals(2, roles.size()); 

    Role loadedUserAdminRole = roleDao.findByName(RoleName.USER_ADMIN); 
    Set<User> users = loadedUserAdminRole.getUsers(); 

    // Test fails: Count is 0 instead of 3 !!!!!!! 
    Assert.assertEquals(3, users.size()); 
} 

UPDATE

dispiace ho dimenticato di dire una cosa. Quando ho testato il codice, ovviamente non ho contrassegnato l'associazione molti a molte due volte in ciascun file di classe. Invece ho usato l'opzione 1 o l'opzione 2 in ogni file di classe.

+0

Dove stai iniziando e terminando le sessioni (o 'EntityManager's in JPA)? Sospetto che se chiudi una sessione dopo il 'save's e ne inizi una nuova prima di' load's, le cose funzioneranno, perché estraggono oggetti dal database piuttosto che dalla cache di sessione. –

+0

La sessione viene aperta durante l'intero metodo di prova. Chiamare getSession() in uno dei metodi di DAO restituisce sempre lo stesso oggetto di sessione purché vengano chiamati in un metodo di test. – Flo

risposta

9

Il problema deriva probabilmente dal fatto che è stata mappata la stessa associazione bidirezionale due volte. Se dici due volte a Hibernate della stessa tabella di join o della colonna di join, c'è un problema. In un'associazione bidirezionale, una delle estremità dell'associazione deve associare l'associazione e l'altra deve indicare a Hibernate che è l'inverso dell'altra estremità, utilizzando l'attributo mappedBy.

Dato che un numero molti-a-molti è completamente simmetrico, scegliere una delle estremità per essere il proprietario (ovvero l'estremità che mappa l'associazione e quindi l'annotazione @JoinTable).L'altro lato è solo l'inverso, e quindi non ha un'annotazione @JoinTable, ma ha un attributo mappedBy.

Esempio:

@Entity 
@Table 
public class User extends BusinessObject { 

    ... 

    // This end is the owner of the association 
    @ManyToMany 
    @JoinTable(name= "user_role", 
       joinColumns = {@JoinColumn(name="user_id")}, 
       inverseJoinColumns = {@JoinColumn(name="role_id")}) 
    private Set<Role> roles = new HashSet<Role>();  
    ... 
} 

@Entity 
@Table 
public class Role extends BusinessObject { 
    ... 

    // This end is not the owner. It's the inverse of the User.roles association 
    @ManyToMany(mappedBy = "roles") 
    private Set<User> users = new HashSet<User>();   

    ... 
} 

Note aggiuntive:

  • targetEntity non è utile, dal momento che Hibernate lo sa grazie al tipo generico del Set. Sarebbe utile se il Set fosse un Set<SomeInterface>
  • CascadeType.ALL non è certamente quello che vuoi. Vuoi eliminare tutti i ruoli di un utente quando elimini un utente? Cosa dovrebbe succedere agli altri utenti che hanno questi ruoli?
  • Si dovrebbe quasi sempre inizializzare entrambe le estremità di un'associazione bidirezionale. Hibernate tiene conto della fine del proprietario (vale a dire la fine senza l'attributo mappedBy) per mantenere l'associazione.
  • Tutto ciò è spiegato nello Hibernate reference documentation. Leggilo: è pieno di informazioni utili e non è difficile da capire.
+0

Modificata l'associazione come consigliato senza alcuna dichiarazione a cascata e inoltre imposta l'associazione su entrambe le estremità. Ora funziona come previsto. – Flo

2

Quando si ha a che fare con un'associazione bidirezionale bidirezionale, è necessario mantenere entrambi i fini dell'associazione. Nel tuo caso, devi aggiungere anche l'utente al ruolo. Aggiunta del ruolo all'utente non è sufficiente a stabilire un'associazione bidirezionale come si può leggere nel libro Java Persistance with Hibernate:

Come sempre, un'associazione bidirezionale (non importa di quale molteplicità) richiede l'impostazione entrambe le estremità della associazione.

Problemi correlati