«1. Обзор

Использование инструмента объектно-реляционного сопоставления, такого как Hibernate, упрощает чтение наших данных в объекты, но может затруднить формирование наших запросов со сложными моделями данных.

Связь «многие ко многим» всегда сложна, но она может быть более сложной, когда мы хотим получить связанные объекты на основе некоторого свойства самого отношения.

В этом уроке мы рассмотрим, как решить эту проблему с помощью аннотации Hibernate @WhereJoinTable.

2. Базовое отношение @ManyToMany

Начнем с простого отношения @ManyToMany. Нам потребуются объекты модели предметной области, объект отношения и некоторые образцы тестовых данных.

2.1. Модель предметной области

Давайте представим, что у нас есть два простых объекта, Пользователь и Группа, которые связаны как @ManyToMany:

@Entity
public class User {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany
    private List<Group> groups = new ArrayList<>();

    // standard getters and setters

}
@Entity
public class Group {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "groups")
    private List<User> users = new ArrayList<>();

    // standard getters and setters

}

Как мы видим, наш объект Пользователь может быть членом более чем одной Группы. организация. Точно так же объект «Группа» может содержать более одного объекта «Пользователь».

2.2. Объект отношения

Для ассоциаций @ManyToMany нам нужна отдельная таблица базы данных, называемая таблицей отношений. Таблица отношений должна содержать по крайней мере два столбца: Первичные ключи связанных сущностей пользователя и группы.

Только с двумя столбцами первичного ключа наше сопоставление Hibernate может представлять эту таблицу отношений.

Однако, если нам нужно поместить дополнительные данные в таблицу отношений, мы должны также определить объект отношения для самого отношения «многие ко многим».

@Entity(name = "r_user_group")
public class UserGroupRelation implements Serializable {

    @Id
    @Column(name = "user_id", insertable = false, updatable = false)
    private Long userId;

    @Id
    @Column(name = "group_id", insertable = false, updatable = false)
    private Long groupId;

}

Давайте создадим для этого класс UserGroupRelation:

Здесь мы назвали объект r_user_group, чтобы мы могли ссылаться на него позже.

public enum UserGroupRole {
    MEMBER, MODERATOR
}

Что касается наших дополнительных данных, предположим, что мы хотим сохранить роль каждого пользователя для каждой группы. Итак, мы создадим перечисление UserGroupRole:

@Enumerated(EnumType.STRING)
private UserGroupRole role;

Затем мы добавим свойство роли в UserGroupRelation:

@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> groups = new ArrayList<>();

Наконец, чтобы настроить его правильно, нам нужно добавить аннотацию @JoinTable к User. Коллекция групп €˜s. Здесь мы укажем имя таблицы соединения, используя r_user_group, имя объекта UserGroupRelation:

2.3. Пример данных

public void setUp() {
    session = sessionFactory.openSession();
    session.beginTransaction();
    
    user1 = new User("user1");
    user2 = new User("user2");
    user3 = new User("user3");

    group1 = new Group("group1");
    group2 = new Group("group2");

    session.save(group1);
    session.save(group2);

    session.save(user1);
    session.save(user2);
    session.save(user3);

    saveRelation(user1, group1, UserGroupRole.MODERATOR);
    saveRelation(user2, group1, UserGroupRole.MODERATOR);
    saveRelation(user3, group1, UserGroupRole.MEMBER);

    saveRelation(user1, group2, UserGroupRole.MEMBER);
    saveRelation(user2, group2, UserGroupRole.MODERATOR);
}

private void saveRelation(User user, Group group, UserGroupRole role) {

    UserGroupRelation relation = new UserGroupRelation(user.getId(), group.getId(), role);
    
    session.save(relation);
    session.flush();
    session.refresh(user);
    session.refresh(group);
}

Для наших интеграционных тестов давайте определим некоторые примеры данных:

Как мы видим, user1 и user2 находятся в двух группах. Кроме того, мы должны заметить, что хотя пользователь1 является МОДЕРАТОРОМ в группе1, в то же время он имеет роль УЧАСТНИК в группе2.

3. Получение отношений @ManyToMany

Теперь, когда мы правильно настроили наши объекты, давайте получим группы объекта User.

3.1. Простая выборка

List<Group> groups = user1.getGroups();

Чтобы получить группы, мы можем просто вызвать метод getGroups() пользователя внутри активного сеанса Hibernate:

[Group [name=group1], Group [name=group2]]    

Наш вывод для групп будет таким:

Но как мы получаем группы пользователя, чья групповая роль — только МОДЕРАТОР?

3.2. Пользовательские фильтры для сущности отношения

Мы можем использовать аннотацию @WhereJoinTable для прямого получения только отфильтрованных групп.

Давайте определим новое свойство как moderatorGroups и поместим на него аннотацию @WhereJoinTable. Когда мы получаем доступ к связанным объектам через это свойство, оно будет содержать только те группы, МОДЕРАТОРОМ которых является наш пользователь.

@WhereJoinTable(clause = "role='MODERATOR'")
@ManyToMany
@JoinTable(
    name = "r_user_group",
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "group_id")
)
private List<Group> moderatorGroups = new ArrayList<>();

Нам нужно добавить предложение SQL where для фильтрации групп по роли МОДЕРАТОРА:

List<Group> groups = user1.getModeratorGroups();

Таким образом, мы можем легко получить группы с указанным предложением SQL where:

[Group [name=group1]]

На выходе будут группы, в которых пользователь имеет только роль МОДЕРАТОРА:

4. Заключение

В этом руководстве мы узнали, как фильтровать коллекции @ManyToMany на основе свойства таблицы отношений, используя Аннотация Hibernate @WhereJoinTable.