父级上没有外键的子级的级联插入

时间:2012-09-10 20:18:40

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

我在网上查了很多帖子,但都没有解决我的问题。如果有人能提供帮助,真的很感激! 我有OneToMany父子关系。当其中一个子组合键是父组件(Level1)的外键时,Hibernate不会级联插入子级(Level2)。我指定了cascade =“all”,无论逆是true还是false,结果都是一样的。没有例外,它只是不插入。下面的打印显示Level1已成功插入,但Level2仅被选中,没有插入。

Hibernate:插入sst.level_1(STATUS,NAME)值(?,?) Hibernate:从sst.level_2 level2x_中选择level2x_.LEVEL_1_ID,level2x_.TIMESEGMENT,level2x_.CATEGORY为CATEGORY4_,其中level2x_.LEVEL_1_ID =?和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>

2 个答案:

答案 0 :(得分:1)

您的映射似乎是正确的。

你的问题看起来像是行

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

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

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生成策略是“已分配”,除非您提供了自定义ID生成器通过创建您自己的CompositeUserType(对于复合ID)以及使用自定义标识符生成器({3}}将您带入hackish方式。但这看起来很丑陋而且有点矫枉过正。

关于保存父级和子级以及在事务中手动设置关系首先删除来自Parent set元素的cascade all选项或将其设置为“none”,以便不存在传递持久性休眠。保存父母只会保存父母而非子女。

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

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

Parent的addChild方法将child添加到集合中,并在一个方便的方法中设置子父级

 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 :(得分: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) );