Hibernate Ассоциация «многие ко многим»: коллекция слева содержит элементы, но коллекция справа пуста - PullRequest
7 голосов
/ 30 августа 2011

У меня проблема со связью многие ко многим в моем слое постоянства. Мой сценарий следующий:

Пользователь может иметь несколько ролей, и к роли может быть прикреплено несколько пользователей. Во время тестов я столкнулся со странным поведением. Я создал объект роли и несколько пользовательских объектов. Роль была назначена каждому из пользователей. После этого пользователи были сохранены с использованием DAO. Затем один из пользователей загружается, чтобы проверить, получил ли он роль, которая была ему передана, перед сохранением объекта пользователя. Вызов getRoles() для пользователя показывает, что роль была установлена ​​правильно.

Чтобы проверить, работает ли обратное направление, объект роли загружается из базы данных с использованием роли DAO. Но вызов getUsers() для объекта роли просто возвращает пустой набор, хотя он должен содержать всех пользователей с этой ролью.

Я дважды проверил таблицу базы данных, но, похоже, все в порядке. Пользователь, роль и таблица user_role были заполнены правильно.

Так почему объект роли не содержит ни одного пользователя?

Я использую Hibernate и Spring со следующими классами.

Класс пользователя

@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>();      

    ... 
}

Роль класса

@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>();          

    ... 
}

Для тестирования я использую следующий код в тестовом классе 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("user1@user.de");        
    user1.getRoles().add(userAdminRole);
    user1.getRoles().add(userRole);
    userDao.save(user1);

    User user2 = new User();
    user2.setEmail("user2@user.de");
    user2.getRoles().add(role);
    userDao.save(user2);

    User user3 = new User();
    user3.setEmail("user3@user.de");
    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

Извините, я забыл упомянуть одну вещь. Когда я тестировал код, я, конечно, не отмечал ассоциацию «многие ко многим» дважды в каждом файле класса. Вместо этого я использовал либо вариант 1, либо вариант 2 в каждом файле класса.

Ответы [ 2 ]

9 голосов
/ 31 августа 2011

Возможно, проблема в том, что вы дважды сопоставили одну и ту же двунаправленную связь. Если вы дважды сообщите Hibernate об одной и той же таблице соединений или столбце соединений, возникнет проблема. В двунаправленной ассоциации один из концов ассоциации должен отобразить ассоциацию, а другой должен сообщить Hibernate, что это инверсия другого конца, используя атрибут mappedBy.

Поскольку многие для многих полностью симметричны, выберите один из концов, чтобы быть владельцем (то есть конец, который отображает ассоциацию и, таким образом, имеет аннотацию @JoinTable). Другая сторона является только обратной, и поэтому не имеет аннотации @JoinTable, но имеет атрибут mappedBy.

Пример:

@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>();          

    ... 
}

Дополнительные примечания:

  • targetEntity бесполезен, поскольку Hibernate знает об этом благодаря универсальному типу Set. Было бы полезно, если бы набор был Set<SomeInterface>
  • CascadeType.ALL, конечно, не то, что вы хотите. Вы хотите удалить все роли пользователя при удалении пользователя? Что должно случиться с другими пользователями, имеющими эти роли?
  • Вы должны почти всегда инициализировать оба конца двунаправленной ассоциации. Hibernate принимает во внимание конец владельца (то есть конец без атрибута mappedBy), чтобы сохранить связь.
  • Все это объясняется в справочной документации по Hibernate . Прочитайте его: в нем много полезной информации, и его нетрудно понять.
2 голосов
/ 31 августа 2011

При работе с двунаправленной ассоциацией «многие ко многим» вы должны поддерживать оба конца ассоциации.В вашем случае, вы также должны добавить пользователя к роли.Добавление роли к пользователю недостаточно для установления двунаправленной ассоциации, как вы можете прочитать в книге Java Persistance with Hibernate :

Как всегда, двунаправленная ассоциация (неважнокакой кратности) требует, чтобы вы установили оба конца ассоциации.

...