6

我在网上查了很多帖子,都没有解决我的问题。如果有人能提供帮助,真的很感激!我有 OneToMany 父子关系。当子复合键之一是父级 (Level1) 的外键时,Hibernate 不会级联插入子级 (Level2)。我指定了 cascade="all" ,无论逆是真还是假,结果都是一样的。也不例外,它根本不插入。下面的打印输出显示 Level1 已成功插入,但仅选择了 Level2,没有插入。

Hibernate: insert into sst.level_1 (STATUS, NAME) values (?, ?)
Hibernate: select level2x_.LEVEL_1_ID, level2x_.TIMESEGMENT, level2x_.CATEGORY as CATEGORY4_ from sst.level_2 level2x_ where level2x_.LEVEL_1_ID=? and level2x_.TIMESEGMENT=?

API 是 Hibernate 4.1.6/Spring 3.1.2。

这是 mySQL 的表定义:

CREATE TABLE level_1
(
ID int NOT NULL PRIMARY KEY AUTO_INCREMENT,
STATUS int,
NAME varchar(255)
);

CREATE TABLE level_2
(
LEVEL_1_ID int,
TIMESEGMENT int,
CATEGORY varchar(2),
PRIMARY KEY (LEVEL_1_ID, TIMESEGMENT),
FOREIGN KEY (LEVEL_1_ID) REFERENCES level_1(ID) ON DELETE CASCADE
);

这是测试代码。

public class Test {

   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      doInsert(context);
   }

   private static void doInsert(ApplicationContext context) {

      Level1 level1 = new Level1();
      Level2 level2 = new Level2();

      level1.setName("LEVEL 1 NAME");
      level1.setStatus(1);
      level1.getLevel2s().add(level2);

      level2.setLevel1(level1);
      level2.setCategory("CA");
      level2.setId(new Level2Id(level1.getId(), 10));

      Level1DAO level1DAO = (Level1DAO) context.getBean("Level1DAO");
      level1DAO.save(level1);
   }
}

Level1的映射:

<hibernate-mapping>
    <class name="com.jc.hibernate.Level1" table="level_1" catalog="sst">
        <id name="id" type="java.lang.Integer">
            <column name="ID" />
            <generator class="identity" />
        </id>
        <property name="status" type="java.lang.Integer">
            <column name="STATUS" />
        </property>
        <property name="name" type="java.lang.String">
            <column name="NAME" />
        </property>
        <set name="level2s" inverse="true" cascade="all">
            <key>
                <column name="LEVEL_1_ID" not-null="true" />
            </key>
            <one-to-many class="com.jc.hibernate.Level2" />
        </set>
    </class>
</hibernate-mapping>

Level2的映射:

<hibernate-mapping>
    <class name="com.jc.hibernate.Level2" table="level_2" catalog="sst">
        <composite-id name="id" class="com.jc.hibernate.Level2Id">
            <key-property name="level1Id" type="java.lang.Integer">
                <column name="LEVEL_1_ID" />
            </key-property>
            <key-property name="timesegment" type="java.lang.Integer">
                <column name="TIMESEGMENT" />
            </key-property>
        </composite-id>
        <many-to-one name="level1" class="com.jc.hibernate.Level1" update="false" insert="false" fetch="select">
            <column name="LEVEL_1_ID" not-null="true" />
        </many-to-one>
        <property name="category" type="java.lang.String">
            <column name="CATEGORY" length="2" />
        </property>
    </class>
</hibernate-mapping>
4

2 回答 2

2

您的映射似乎是正确的。

你的问题看起来像这条线

level2.setId(new Level2Id(level1.getId(), 10));

执行此行时,level1 仍然没有分配其 id,因此将为 null。由于level_1_id和segment是复合主键,不能为空。所以你需要先做

Integer generatedId = (Integer) Session.save(level1)

这会立即插入数据库并返回标识符。然后你可以使用标识符来创建Lever2Id

level2.setId(new Level2Id(generatedId  , 10));

我使用您提供的映射在 MySql 上进行了尝试,并且工作正常。

如果我不先保存第level1一个,它会给我错误

Column 'LEVEL_1_ID' cannot be null

更新

不幸的是,当子级中有复合键并且复合键中的一列引用父级的主键时,级联似乎不会自动工作。因此,您必须手动设置 id 和关系,如上所示。

请注意,如果 Child 具有单个列 id 并且您的关系是一对一的,那么 Hibernate 可以确定子主列来自 Parent 并且可以自动设置。看这里这里

文档也在这里提出了这个建议

“你不能使用 IdentifierGenerator 来生成复合键。相反,应用程序必须分配自己的标识符。”</p>

代表复合 id 映射的休眠类是 org.hibernate.mapping.Component (用于组件、复合元素、复合标识符等),默认 id 生成策略是“assigned”,除非您提供了一个自定义 id 生成器您可以通过创建自己的 CompositeUserType (用于复合 Id)并使用此处提到的自定义标识符生成器来采用一种骇人听闻的方式。但这看起来很丑陋而且矫枉过正。

关于在事务中手动保存父子节点和设置关系首先从父集元素中删除级联所有选项或将其设置为“无”,这样 Hibernate 就没有传递持久性。保存父母只会保存父母而不是孩子。

然后使用您可用的所有属性准备父级和子级,并像这样从两侧设置对象关系(以确保模型一致性)。

 Parent parent = new Parent();
    parent.setStatus( 1  );
    parent.setName( "Parent" );  
    ChildId childId = new ChildId();    
    Child child = new Child(childId);    
    child.setCategory( "TH" );    
    parent.addChild( child );
    child.getId().setTimesegment( 10 );

父母的 addChild 方法将孩子添加到集合中,并以一种方便的方法设置孩子的父母

 public void addChild(Child child){
    if (child !=null){
      getChildrens().add( child );
      child.setParent( this );
    }

  }

然后在 DAO 中有一个以 Child 作为参数并且也是事务性的方法,就像这样(尽管更好的方法是使服务层事务性而不是 DAO 层)

public void save(Child child){

     //get Session 
    Session session =.......
    Transaction tx = session.beginTransaction();
    Integer generatedId = (Integer)session.save( child.getParent() );
    ChildId childId = child.getId();
    childId.setChildId( generatedId );     
    session1.save( child );   
    tx1.commit();
    HibernateUtil.closeSession();
}

到目前为止,这是我可以建议的。

于 2012-09-11T11:11:06.343 回答
0

只是为了更新。我删除了复合 ID。相反,在子表上使用数据库生成的代理键,以及父 id 的外键。这适用于与 cascade="all" 的一对多关系。拯救父母一步拯救孩子。这并没有解决原来的问题,而是一个替代方案。

CREATE TABLE level_11 (
ID int NOT NULL PRIMARY KEY AUTO_INCREMENT, 
STATUS int, NAME varchar(255) );

CREATE TABLE level_22 ( ID int NOT NULL PRIMARY KEY AUTO_INCREMENT, 
LEVEL_11_ID int NOT NULL, 
TIMESEGMENT int NOT NULL, 
FOREIGN KEY (LEVEL_11_ID) REFERENCES level_11(ID) ON DELETE CASCADE, 
CONSTRAINT UQ_LEVEL2 UNIQUE (LEVEL_11_ID, TIMESEGMENT) );
于 2012-10-02T04:33:49.957 回答