【问题标题】:No cascade insert for child with foreign key on parent没有父级外键的子级级联插入
【发布时间】:2012-09-03 17:45:54
【问题描述】:

我在网上查了很多帖子,都没有解决我的问题。如果有人能提供帮助,真的很感激! 我有 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>

【问题讨论】:

    标签: hibernate foreign-keys one-to-many cascade


    【解决方案1】:

    您的映射似乎是正确的。

    你的问题看起来像一行

    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 并且可以自动设置。见herehere

    文档也建议这样做here

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

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

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

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

     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();
    }
    

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

    【讨论】:

    • 感谢您的回复。导致问题的级别 1 id 为空是有道理的。在这种情况下,我将如何配置事务以使其对级别 1 和 2 具有原子性?
    • 查看我在事务中同时保存孩子和父母的更新
    【解决方案2】:

    只是为了更新。我删除了复合 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) );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多