Не могу заставить многих работать многих с Hibernate 3.3.1GA - PullRequest
2 голосов
/ 26 мая 2009

У меня есть простое сопоставление «многие ко многим»:

@Entity
@Table(name="ITEM")
public static class Item
{
    @Id
    @GeneratedValue
    long id;
    @Column
    String name;
    @ManyToMany(cascade={ CascadeType.ALL })
    Set<Category> categories = new HashSet<Category> ();
}

@Entity
@Table(name="CATEGORY")
public static class Category
{
    @Id
    @GeneratedValue
    long id;
    @Column
    String name;
}

И этот контрольный пример:

public void testPersist() throws Throwable
{
    Category one = new Category ();
    one.name = "one";
    em.persist (one);
    System.out.println ("one.id="+one.id);

    Category two = new Category ();
    two.name = "two";
    em.persist (two);
    System.out.println ("two.id="+two.id);

    Item item = new Item ();
    item.name = "item";
    item.categories.add (one);
    item.categories.add (two);
    em.persist (item);
    long id = item.id;
    System.out.println ("item.id="+item.id);
    System.out.println ("item.categories="+item.categories);

    em.clear ();

    item = em.find (Item.class, id);
    System.out.println ("item.categories="+item.categories);
    assertEquals (item.categories.toString (), 2, item.categories.size ());
}

Тест не пройден. В журнале я вижу:

SchemaUpdate - create table CATEGORY (id bigint generated by default as identity (start with 1), name varchar(255), primary key (id))
SchemaUpdate - create table ITEM (id bigint generated by default as identity (start with 1), name varchar(255), primary key (id))
SchemaUpdate - create table ITEM_CATEGORY (ITEM_id bigint not null, categories_id bigint not null, primary key (ITEM_id, categories_id))
SchemaUpdate - alter table ITEM_CATEGORY add constraint FK5C7030AA7C924DF7 foreign key (categories_id) references CATEGORY
SchemaUpdate - alter table ITEM_CATEGORY add constraint FK5C7030AA66304535 foreign key (ITEM_id) references ITEM

Итак, правильные таблицы созданы, двойной первичный ключ в порядке, внешние ключи верны.

Сохранение item не делает правильных вещей:

[DEBUG] org.hibernate.SQL - insert into CATEGORY (id, name) values (null, ?)
[TRACE] org.hibernate.type.StringType - binding 'one' to parameter: 1
[DEBUG] org.hibernate.SQL - call identity()
one.id=1
[DEBUG] org.hibernate.SQL - insert into CATEGORY (id, name) values (null, ?)
[TRACE] org.hibernate.type.StringType - binding 'two' to parameter: 1
[DEBUG] org.hibernate.SQL - call identity()
two.id=2
[DEBUG] org.hibernate.SQL - insert into ITEM (id, name) values (null, ?)
[TRACE] org.hibernate.type.StringType - binding 'item' to parameter: 1
[DEBUG] org.hibernate.SQL - call identity()
item.id=1
item.categories=[ch.globus.bonus.ManyToManyTest$Category@1d532ae, ch.globus.bonus.ManyToManyTest$Category@42a6eb]

Как видите, сам элемент сохраняется, но не содержит набор категорий (вставки в таблицу объединения отсутствуют). Но в наборе есть значения!

Когда он читает элемент, он запрашивает таблицу соединения:

[DEBUG] org.hibernate.SQL - select manytomany0_.id as id9_0_, manytomany0_.name as name9_0_ from ITEM manytomany0_ where manytomany0_.id=?
[TRACE] org.hibernate.type.LongType - binding '1' to parameter: 1
[TRACE] org.hibernate.type.StringType - returning 'item' as column: name9_0_
[DEBUG] org.hibernate.SQL - select categories0_.ITEM_id as ITEM1_1_, categories0_.categories_id as categories2_1_, manytomany1_.id as id10_0_, manytomany1_.name as name10_0_ from ITEM_CATEGORY categories0_ left outer join CATEGORY manytomany1_ on categories0_.categories_id=manytomany1_.id where categories0_.ITEM_id=?
[TRACE] org.hibernate.type.LongType - binding '1' to parameter: 1
item.categories=[]

но, конечно, он ничего не может найти. В чем дело? Почему hibernate рассматривает таблицу соединений для find(), а не для persist()?

Я использую Hibernate 3.3.1, Аннотации Hibernate 3.4.0

[РЕДАКТИРОВАТЬ] Чтобы исправить проблему, я попытался ввести двунаправленное отображение. Сначала я добавил этот код в Category:

    /* BEGIN FIX1: made join bidrectional */
    @ManyToMany(cascade={ CascadeType.ALL })
    Set<Item> items = new HashSet<Item> ();
    /* END FIX1: made join bidrectional */

Затем я изменил тест, чтобы обновить обе стороны:

    item.categories.add (two);
    /* BEGIN FIX1: made join bidrectional */
    one.items.add (item);
    two.items.add (item);
    /* END FIX1: made join bidrectional */
    em.persist (item);
    /* BEGIN FIX1: made join bidrectional */
    em.persist (one);
    em.persist (two);
    /* END FIX1: made join bidrectional */

Я даже пошел, чтобы продолжить категории снова. Результат: ничего. Hibernate счастливо игнорирует отображение «многие ко многим».

В качестве второго исправления я попытался добавить аннотацию @JoinTable, предложенную zoidbeck:

    @ManyToMany(cascade={ CascadeType.ALL })
    /* BEGIN FIX2: Added JoinTable */
    @JoinTable(name = "ITEM_CATEGORY",
            joinColumns={@JoinColumn(name="itm_id")},
            inverseJoinColumns={@JoinColumn(name="cat_id")}
    )
    /* END FIX2: Added JoinTable */
    Set<Category> categories = new HashSet<Category> ();

Опять ничего.

Ответы [ 2 ]

2 голосов
/ 27 мая 2009

Причина в оптимизации в Hibernate: тест никогда не сбрасывает сеанс. Таким образом, все, что происходит, - это то, что Hibernate запоминает изменения, которые необходимо будет выполнить, во внутреннем кеше, но поскольку транзакция никогда не фиксируется, она никогда не видит причины для генерации SQL для обновления таблицы соединения.

Но почему тогда я вижу вставку предмета? Потому что Hibernate нужен свой идентификатор для объединения. Поэтому он должен спросить базу данных «какой идентификатор получит этот объект?».

Решение состоит в том, чтобы заставить Hibernate очистить кэш:

    Session session = (Session)em.getDelegate ();
    session.flush ();

Вот полный рабочий тестовый пример:

@Entity
@Table(name="ITEM")
public static class Item
{
    @Id
    @GeneratedValue
    long id;
    @Column
    String name;
    @ManyToMany(cascade={ CascadeType.ALL })
    Set<Category> categories = new HashSet<Category> ();
}

@Entity
@Table(name="CATEGORY")
public static class Category
{
    @Id
    @GeneratedValue
    long id;
    @Column
    String name;
}

public void testPersist() throws Throwable
{
    final Item item = prepareDB ();

    long id = item.id;
    assertTrue ("ID: "+id, id > 0);

    // Clear cache to make sure the objects are loaded again from DB
    em.clear ();

    Item item2 = em.find (Item.class, id);
    assertEquals (item2.categories.toString (), 2, item2.categories.size ());

    delete (item2);

    item2 = em.find (Item.class, id);
    assertNull (item2);
}

public void flush ()
{
    Session session = (Session)em.getDelegate ();
    session.flush ();
}

@Transactional
public void delete (Item item2)
{
    em.remove (item2);
    // Force delete
    flush ();
}

@Transactional
public Item prepareDB ()
{
    final Category one = new Category ();
    one.name = "one";
    em.persist (one);
    System.out.println ("one.id="+one.id);

    final Category two = new Category ();
    two.name = "two";
    em.persist (two);
    System.out.println ("two.id="+two.id);

    final Item item = new Item ();
    item.name = "item";
    item.categories.add (one);
    item.categories.add (two);
    em.persist (item);

    // Force update of join table
    flush ();

    return item;
}
0 голосов
/ 27 мая 2009

Насколько я знаю, вы должны полностью указать linktable:

@Entity
@Table(name="ITEM")
public static class Item
{
    @Id
    @GeneratedValue
    long id;

    @Column
    String name;

    @ManyToMany(cascade={ CascadeType.ALL })
    @JoinTable(name = "ITEM_CATEGORY",
      joinColumns = { @JoinColumn(name="item_id") },
      inverseJoinColumns = { @JoinColumn(name="categories_id")}
    Set<Category> categories = new HashSet<Category> ();
}

Я не уверен, нужно ли указывать ManyToMany двунаправленным способом, но если это так, то это будет сопоставление другой стороны:

@ManyToMany(mappedBy="categories")  // map info is in item class
private Set<Item> items;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...