6

私はインターネット上の多くの投稿をチェックしましたが、私の問題に対処するものはありません。誰かが助けてくれれば本当に感謝します!OneToMany の親子関係があります。子複合キーの 1 つが親 (レベル 1) への外部キーである場合、Hibernate は挿入子 (レベル 2) をカスケードしません。cascade="all" を指定しましたが、逆が true か false かに関係なく、結果は同じです。例外はなく、単に挿入しません。以下の出力は、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、いずれも null にすることはできません。したがって、最初に行う必要があります

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

これにより、データベースに即座に挿入され、識別子が返されます。次に、識別子を使用して作成できますLever2Id

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

あなたが提供したマッピングでMySqlでこれを試してみましたが、うまくいきました。

最初に最初に保存しないlevel1と、エラーが発生します

Column 'LEVEL_1_ID' cannot be null

アップデート

残念ながら、子に複合キーがあり、複合キーの列の1つが親の主キーを参照している場合、カスケードは自動的に機能しないようです。したがって、上記のように ID と関係を手動で設定する必要があります。

子に単一の列 ID があり、関係が 1 対 1 である場合、Hibernate は子のプライマリ列が親から取得され、自動的に設定できることを認識できることに注意してください。こちらとこちらをご覧ください

ドキュメントもここでこれを示唆しています

「IdentifierGenerator を使用して複合キーを生成することはできません。代わりに、アプリケーションは独自の識別子を割り当てる必要があります。」</p>

複合 ID マッピングを表す hibernate クラスは org.hibernate.mapping.Component (コンポーネント、複合要素、複合 ID などに使用されます) であり、デフォルトの ID 生成戦略は「割り当て済み」です。独自の CompositeUserType (複合 ID 用) を作成し、ここで説明されているようにカスタム識別子ジェネレーターを使用することで、ハックな方法にします。しかし、これは醜く、やり過ぎに見えます。

親と子の両方をトランザクションに保存し、関係を手動で設定することについて まず、Parent set 要素から cascade all オプションを削除するか、「none」に設定して、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 メソッドは、子をコレクションに追加するだけでなく、1 つの便利なメソッドで子の親を設定します

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

  }

次に、Child をパラメーターとして取り、トランザクション対応の DAO のメソッドを作成するには、次のようにします (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" との 1 対多の関係で機能します。親を保存すると、子がワンステップで保存されます。これは元の質問を解決するものではありませんが、別の方法です。

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 に答える