ManyToMany (с дополнительными столбцами) с помощью @ElementCollection и java.util.Map?

Я пытаюсь оптимизировать классы @Entity. Возьмем общий случай отношения a User <-> Group. Я также хочу сохранить дату, когда было установлено отношение. Раскладка таблицы:

 USER      GROUP_USER      GROUP
------    ------------    -------
 id        user_id         id
 name      group_id        name
           createdAt

Со следующими тремя классами:

  • User с @OneToMany List<GroupUser>
  • GroupUser с @ManyToOne User и @ManyToOne Group
  • Group с @OneToMany List<GroupUser>

Это работает, но это отстой для обычных случаев использования. Например: дайте мне все группы для User user:

List<Group> groups;
for(GroupUser gu : user.getGroupUsers) {
    groups.add(gu.getGroup());
}

Тогда идея использования Map пришла мне в голову. Поэтому я создал следующие 3 класса:

@Entity
class User {
   @Column(name="id")
   UUID id;

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

   @ElementCollection
   @CollectionTable(name="Group_User")
   Map<Group, GroupUserRelationData> groups;
}

@Entity
class Group {
   @Column(name="id")
   UUID id;

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

   @ElementCollection
   @CollectionTable(name="Group_User")
   Map<User, GroupUserRelationData> users;
}

@Embeddable
class GroupUserRelationData {
  @Column(name="createdAt")
  DateTime createdAt;
}

Он делает, создавая 3 таблицы, как ожидалось, но столбцы внутри таблицы Group_User являются странными:

 USER      GROUP_USER      GROUP
------    ------------    -------
 id        User_id         id
 name      users_KEY       name
           Group_id
           group_KEY
           createdAt

Я уверен, что мне не хватает некоторых вещей... Похоже, я должен указать JoinColumn. Но какой правильный путь?

Какая разница между @CollectionTable(joinColumns=...) и @MapKeyColumn и @MapKeyJoinColumn. Должен ли я установить все из них?

Или мне нужно использовать @ManyToMany вместо @ElementCollection?

Я просто хочу получить ту же таблицу, что и наверху, используя java.util.Map.


РЕДАКТИРОВАТЬ № 1: Кроме того, база данных должна иметь правильные ограничения для таблицы:

  • Композитный первичный ключ (Group_id, User_id)

В настоящий момент он создает составную PK (Group_id, users_KEY), которая кажется мне довольно странной. И он также создает 4 (!!) индекса, по одному для каждого столбца User_id, Group_id, users_KEY и groups_KEY.


EDIT # 2: Я нашел веб-сайт, в котором рассказывается, когда использовать аннотации: http://kptek.wordpress.com/2012/06/26/collection-mapping/

Плохая вещь: я до сих пор не знаю ПОЧЕМУ...

Решение:

После нескольких попыток я придумал следующий код, который делает именно то, что я хочу. Я должен использовать все эти аннотации, иначе он создает дополнительные столбцы:

@Entity
class User {
   @Column(name="id")
   UUID id;

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

   @ElementCollection
   @CollectionTable(name="Group_User",
     [email protected](name="User_Id"))
   @MapKeyJoinColumn(name="Group_Id")
   Map<Group, GroupUserRelationData> groups;
}

@Entity
class Group {
   @Column(name="id")
   UUID id;

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

   @ElementCollection
   @CollectionTable(name="Group_User",
     [email protected](name="Group_Id"))
   @MapKeyJoinColumn(name="User_Id")
   Map<User, GroupUserRelationData> users;
}

@Embeddable
class GroupUserRelationData {
  @Column(name="createdAt")
  DateTime createdAt;
}

Мне все еще хотелось бы объяснить, что именно делают эти аннотации, и почему поведение по умолчанию создало четыре столбца и странный первичный ключ.

Ответ 1

Для карты требуется как ключ "один ко многим", так и ключ карты.

В первом примере, Hibernate вычисляет, что ему нужен FK от Group.id, чтобы соответствовать GroupUser.group_id для загрузки связанных пользователей. Но поскольку вы не указали ключ карты, ему нужно выяснить где взять его из?

Итак, Hibernate предположил, что вам нужен @MapKeyColumn, и поскольку имя столбца не указано, используется имя свойства, за которым следует символ подчеркивания, за которым следует KEY (например, user_KEY).

Вот почему у вас были две дополнительные колонки. Когда вы поставили @MapKeyJoinColumn(name="User_Id"), тогда Карта знала, куда взять ключ.