2009-05-26 48 views
2

我有一个简单许多一对多映射:无法获得许多对许多使用Hibernate 3.3.1GA工作

@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 

所以创建正确的表,双主键是OK,外键是正确的。

的持久化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=[[email protected], [email protected]] 

正如你所看到的,项目本身是坚持而不是设置与类别(在插入到连接表丢失) 。但是这套设备有价值!

读取的项目,它的查询联接表:

[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=[] 

但当然,它不能发现任何东西。怎么了?为什么冬眠考虑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 */ 

我还跟再次persiste的类别。结果:没有。 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

原因是Hibernate中的优化:测试永远不会刷新会话。所以发生的一切就是Hibernate会记住它在内部缓存中需要做的改变,但由于事务永远不会被提交,所以它永远不会看到生成SQL来更新连接表的原因。

但是,为什么我会看到该项目的插入,然后呢?因为Hibernate需要它的ID加入。所以它必须询问数据库“这个对象会得到什么ID?”。

解决的办法是强制休眠刷新其缓存:

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

据我所知,你必须完全指定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>(); 
} 

我不知道wheather你需要在一个双向的方式来指定多对多,但即便如此,这将是的映射其他方面:

@ManyToMany(mappedBy="categories") // map info is in item class 
private Set<Item> items; 
+0

不,测试仍然失败。有关详细信息,请参阅我对我的问题的编辑。 – 2009-05-27 07:28:04

+0

对不起,没有为你工作,但很高兴听到你终于找到了解决方案。 – zoidbeck 2009-05-27 22:32:04