2015-12-20 26 views
6

Ho un problema con una relazione molti-a-molti in letargo: quando rimuovo un elemento dal mio set, non viene rimosso nel mio database. So che ci sono tonnellate di problemi simili, ma non sono riuscito a sistemare il mio leggendoli.Hibernate many-to-many remove relation

Ho scritto un caso di test JUnit per questo. La mia associazione è tra gli edifici e gli utenti:

@Test 
public void testBuildingManyToMany(){ 
    //Create 2 buildings 
    Building building = createBuilding("b1"); 
    Building building2 = createBuilding("b2"); 
    //Create 1 user 
    User user = createUser("u1"); 

    //Associate the 2 buildings to that user 
    user.getBuildings().add(building); 
    building.getUsers().add(user); 

    user.getBuildings().add(building2); 
    building2.getUsers().add(user); 

    userController.save(user); 
    user = userController.retrieve(user.getId()); 
    Assert.assertEquals(2, user.getBuildings().size());//Test OK 

    //Test 1: remove 1 building from the list 
    user.getBuildings().remove(building); 
    building.getUsers().remove(user); 
    userController.save(user); 

    //Test 2: clear and add 
    //user.getBuildings().clear(); 
    //user.getBuildings().add(building); 
    //userController.save(user); 
    //user = userController.retrieve(user.getId()); 
    //Assert.assertEquals(1, user.getBuildings().size()); 
} 

Qui è l'errore che ho ottenuto:

... 
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) 
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) 
Hibernate: delete from building_useraccount where userid=? and buildingid=? 
Hibernate: insert into building_useraccount (userid, buildingid) values (?, ?) 
4113 [main] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 23505, SQLState: 23505 
4113 [main] ERROR org.hibernate.util.JDBCExceptionReporter - Unique index or primary key violation: "PRIMARY_KEY_23 ON PUBLIC.BUILDING_USERACCOUNT(BUILDINGID, USERID) VALUES (/* key:0 */ 201, 201)"; SQL statement: 
insert into building_useraccount (userid, buildingid) values (?, ?) [23505-176] 

Quando io commento "Test 1" e rimuovere il commento le righe "di prova 2", vado il seguente errore:

junit.framework.AssertionFailedError: 
Expected :1 
Actual :2 

Qui sono le mie classi hbm.xml:

<hibernate-mapping default-lazy="true"> 
    <class name="my.model.pojo.Building" table="building"> 
    <cache usage="read-write" /> 
    <id name="id" column="id" type="java.lang.Long"> 
     <generator class="sequence"> 
      <param name="sequence">building_id_sequence</param> 
     </generator> 
    </id> 
    <property name="name" type="java.lang.String" column="name" not-null="true" /> 
    ... 
    <set name="users" cascade="none" lazy="true" inverse="true" table="building_useraccount"> 
     <key column="buildingid" /> 
     <many-to-many class="my.model.pojo.User" column="userid" /> 
    </set> 
</class> 
</hibernate-mapping> 

e

<hibernate-mapping default-lazy="true"> 
<class name="my.model.pojo.User" table="useraccount"> 
    <cache usage="read-write" /> 
    <id name="id" column="id" type="java.lang.Long"> 
     <generator class="sequence"> 
      <param name="sequence">useraccount_id_sequence</param> 
     </generator> 
    </id> 
    <property name="login" type="java.lang.String" column="login" not-null="true" unique="true" length="40" /> 

    ... 
    <set name="buildings" cascade="none" lazy="false" fetch="join" table="building_useraccount"> 
     <key column="userid" /> 
     <many-to-many class="my.model.pojo.Building" column="buildingid" /> 
    </set> 
</class> 
</hibernate-mapping> 

e le classi

public class User implements Serializable, Identifiable { 

private static final long serialVersionUID = 1L; 
private int hashCode; 

private Long id; 
private String login; 

private Set<Building> buildings = new HashSet<Building>(); 

public boolean equals(Object value) { 
    if (value == this) 
     return true; 
    if (value == null || !(value instanceof User)) 
     return false; 
    if (getId() != null && getId().equals(((User) value).getId())) 
     return true; 
    return super.equals(value); 
} 

public int hashCode() { 
    if (hashCode == 0) { 
     hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode(); 
    } 
    return hashCode; 
} 

/* Getter/Setter ... */ 

e

public class BuildingBase implements Serializable, Identifiable { 

private static final long serialVersionUID = 1L; 
private int hashCode; 

private Long id; 
private String name; 

private Set<User> users = new HashSet<User>(); 

public boolean equals(Object value) { 
    if (value == this) 
     return true; 
    if (value == null || !(value instanceof Building)) 
     return false; 
    if (getId() != null && getId().equals(((Building) value).getId())) 
     return true; 
    return super.equals(value); 
} 

public int hashCode() { 
    if (hashCode == 0) { 
     hashCode = (getId() == null) ? super.hashCode() : new HashCodeBuilder().append(getId()).toHashCode(); 
    } 
    return hashCode; 
} 

/* Getter/Setter ... */ 

EDIT: Aggiungi implementazione UserController, per la transazione

@Transactional(readOnly = false, propagation = Propagation.REQUIRED) 
public User save(User user) throws ServiceException { 
    validate(user);//Validation stuffs 
    return userDAO.update(user); 
} 

L'userDAO:

public class UserDAOImpl extends HibernateDAOImpl<User> implements UserDAO { 
} 

E il HibernateDAOImpl:

public class HibernateDAOImpl<T> implements DAO<T> { 

    public T update(T entity) { 
     return executeAndCreateSessionIfNeeded(new HibernateAction<T>() { 
      @Override 
      public T execute(Session session) { 
       return (T) session.merge(entity); 
      } 
     }); 
    } 

    protected <E> E executeAndCreateSessionIfNeeded(HibernateAction<E> action) { 
     Session session = null; 
     try { 
      session = sessionFactory.getCurrentSession(); 
      return executeAction(action, session); 
     } finally { 
      if (session != null) { 
       session.close(); 
      } 
     } 
    } 

} 
+0

Si prega di pubblicare l'implementazione del metodo 'userController.save'. Inoltre, quali sono i limiti della transazione? –

+0

Ho aggiunto alcune implementazioni. La transazione funziona abbastanza bene, poiché viene utilizzata con successo ovunque nel codice. Si noti inoltre che la cancellazione degli edifici (con user.getBuildings(). Clear()) funziona anche e svuota la tabella del database many-to-many! Solo la rimozione stranamente non funziona ... – Asterius

risposta

0

La modifica della proprietà in cascata non ha risolto il problema. Alla fine decido di gestire me stesso la relazione molti-a-molti creando un oggetto per il tavolo intermedio e gestendolo per conto mio. È un po 'più di codice, ma fornisce un comportamento coerente per ciò che volevo ottenere.

1

Perché cascade="none"?

È necessario utilizzare cascade="detached,merge,refresh,persist" (non eliminare!) Per aggiornare le rimozioni nelle raccolte.

1

La sostituzione di cascade='none' di cascade='all' sulla relazione buildings deve risolvere il problema.

Dato che si sta salvando l'utente, per poter aggiornare anche i molti a molti nel DB, è necessario eseguire in cascata le modifiche sulla relazione da parte dell'utente.

10

Il CascadeType.REMOVEdoesn't have sense for many-to-many associations perché, se impostato su entrambi i lati, potrebbe attivare una cancellazione della catena tra genitori e figli e ritorno ai genitori. Se lo si imposta solo sul lato genitore, è possibile riscontrare problemi nel caso in cui un figlio di eliminazione sia ancora referenziato da altri genitori.

Per citare il Hibernate docs:

It does not usually make sense to enable cascade on a many-to-one or many-to-many association. In fact the @ManyToOne and @ManyToMany don't even offer a orphanRemoval attribute. Cascading is often useful for one-to-one and one-to-many associations.

0

temo che quello che stai facendo non è davvero una buona idea con Hibernate, anche se è uno dei compiti più usuale si farebbe con una relazione. Il modo per ottenere ciò che vuoi è usare le cascate ma, come dice Vlad Mihalcea, questo può finire per cancellare uno o l'altro capo della relazione e non solo la relazione stessa.

Come risposta adeguata, vorrei dirti cosa direbbe un insegnante ... Hai davvero una relazione n: m? Sei sicuro di non avere un'entità da solo? N: Le relazioni M sono molto rare da trovare e in genere indicano che la modellazione è sbagliata. Anche quando questo non è il caso e in realtà hai un n: m, questo dovrebbe rimanere nel modello, non dimenticare mai che stai usando un ORM per collegare il modello ACTUAL al tuo modello java in modo da poter avere un'entità in Java con 1: n relazioni su ciascuna estremità e memorizzarla nella tabella delle relazioni.

Cordiali saluti!